481 Commits
Author SHA1 Message Date
Oleg Isonen a2e01a8ebe fix: Export configured Webhook Form methods (#6192)
## Summary

- Add an optional metadata contract for copying allowlisted resource
fields into generated component props.
- Pass action resources into page component generation.
- Configure Webhook Form's resource-bound `action` prop to generate the
form's `method` prop.
- Preserve explicitly authored props over generated resource props.
- Add generic generator, schema safety, rendered static-export
regression coverage, and updated generated fixture output.

## Root cause

Builder stores a Webhook Form's selected method on its action resource,
not as an ordinary instance prop. Component generation serialized the
resource-bound action identifier but had no generic contract for copying
safe resource fields into component props. The generated component
therefore received `action={"action"}` without the configured method,
and plain HTML rendering omitted the `method` attribute.

## Implementation

Resource prop metadata can now declare same-named fields using
`generatedProps`:

```ts
generatedProps: ["method"]
```

The component generator resolves those declarations generically; it
contains no Webhook Form component checks. The schema currently
allowlists only `method`, so resource URLs, headers, bodies, and other
potentially sensitive configuration cannot be generated as component
props. An explicit instance prop with the same name takes precedence.

Dynamic routed Webhook Forms retain their existing browser-to-server
POST behavior. This change preserves configured resource metadata in
generated component output and does not add static Webhook submission
support.

## Todo

- [x] Reproduce the missing method from an existing resource-bound
Webhook Form.
- [x] Demonstrate the generator regression test fails against the
previous output.
- [x] Define a typed, allowlisted generated-props contract for
resources.
- [x] Apply generated props generically without component-name checks.
- [x] Preserve explicit instance props.
- [x] Verify a real SSG prerender contains `method="post"`.
- [x] Verify unsafe resource fields such as `url` cannot be generated.
- [x] Give the build/prerender regression test the established 30-second
timeout.
- [x] Regenerate and verify the affected Webstudio Features fixture.
- [x] Run affected lint, typechecks, unit suites, builds, and CLI
prebuild tests.
- [x] Review documentation impact; no update is needed because
configuration and static-site support constraints are unchanged.

## Verification

- Regression test before implementation: failed because resource-bound
forms contained `action` but no generated `method`.
- `@webstudio-is/sdk`: 517 tests passed; focused schema test passed
after the rename.
- `@webstudio-is/sdk-components-react`: 126 passed, 1 skipped.
- `@webstudio-is/react-sdk`: 140 tests passed; focused generator test
passed after the rename.
- CLI prebuild: 59 tests passed; focused rendered static HTML test
passed in 2.7 seconds with a 30-second test timeout.
- Webstudio Features fixture: regeneration is idempotent; typecheck and
production build passed.
- Typechecks: SDK, SDK components, React SDK, and CLI.
- Targeted formatting and `oxlint --deny-warnings`.
- React SDK and CLI builds.
- `git diff --check origin/main...HEAD`.
- Agent evaluations were not run because approval was not requested.

Closes #6191
2026-08-19 02:28:07 +02:00
Oleg Isonen 2df130171f feat: Resolve asset references as structured metadata (#6184)
## Summary

- add exact `$ref` asset values that resolve to Asset Manager metadata
- keep plain local asset paths returning URL strings for backward
compatibility
- derive reference metadata from the canonical asset object so newly
added asset fields flow through automatically
- let Assets query output fields select only the metadata returned,
including nested arrays
- carry the same resolved shape through preview, generated preview,
published runtime, and static export
- document the syntax and accessible image binding in Content Engine and
MCP docs

## Design

The source document declares only the relationship:

```yaml
featureImage:
  $ref: ./assets/hero.png
```

The Assets query controls projection with fields such as
`properties.featureImage.src` and `properties.featureImage.description`.
Selecting `properties.featureImage` returns the canonical Asset Manager
asset metadata together with the resolved `src`. The Content Engine
runtime contract accepts the canonical asset object instead of
maintaining a second metadata field list; `url` becomes `src`, and
internal `contentRef` is not exposed.

Plain strings retain their existing URL-string result. Existing document
`$ref` behavior is preserved by resolving structured asset metadata only
when the target is a known non-document asset.

## Verification

- [x] Content Engine full test suite: 298 tests
- [x] SDK full test suite: 517 tests
- [x] Asset repository tests: 39 tests
- [x] CLI prebuild tests: 58 tests
- [x] Content Engine, SDK, Asset Uploader, CLI, and Project Build
typechecks
- [x] Repository lint
- [x] Content Engine and MCP documentation generation and tests
- [x] Generated API check
- [x] Local fixture build and generated fixture update
- [x] New behavior tests demonstrated fail before/pass after the
implementation
- [ ] Complete fixture sync (development API currently rejects this
branch's newer CLI protocol; local fixture build passes)
- [ ] Agent evaluations (not run; require explicit approval)

## Checklist

- [x] Local structured references return field-selected metadata
- [x] Newly added canonical asset fields flow through without a Content
Engine whitelist change
- [x] Plain-path queries remain backward compatible
- [x] Nested object and array projection
- [x] External URL strings remain usable
- [x] Missing or null descriptions allow `??` fallback to the title
- [x] Expanded metadata counts toward query result-size limits
- [x] Documentation impact reviewed and Content Engine and MCP docs
updated
- [ ] Missing asset targets return a non-failing unresolved value and
dedicated diagnostic
- [ ] Apply the resource and Image binding changes to the separate
Webstudio blog project

## Known limitation

A `$ref` whose asset target no longer exists is not yet converted into
an unresolved asset value with a dedicated diagnostic. This PR
intentionally does not claim that acceptance criterion yet.

Ref #6179
2026-08-19 01:11:41 +02:00
Oleg Isonen d4a876bc35 fix: Report terminated MCP single-op calls (#6187)
## Summary

- return structured `MCP_CALL_TERMINATED` output when a one-shot MCP
call receives SIGHUP, SIGINT, or SIGTERM, or reaches `beforeExit`
without a result
- reuse the existing bounded host-cleanup and signal-preservation path
from `mcp run`
- cover the reported anchor → inline SVG → `pointer-events: none`
fragment through the real MCP normalization path

## Evidence and scope

The reported fragment itself does not hang: it normalizes in about 20–30
ms on both CLI 0.290.0 and current main, including Node 26.0.0. The
reproducible gap is in the surrounding process lifecycle: `mcp run`
reports interrupted calls, while `mcp single-op-call` had no termination
handlers. An external caller timeout could therefore end a one-shot call
without a structured result.

The original long-running project-session condition could not be
reproduced with the available credentials. This PR fixes the missing
termination diagnostic; it does not claim that nested SVG parsing caused
the stall.

## Technical choices

- Install termination handling before input parsing and project-session
startup so every active phase is covered.
- Re-check termination after every asynchronous preflight phase so
refreshes and mutations cannot start during cleanup.
- Stop reporting the call as active once success or failure output is
ready, preventing duplicate JSON during cleanup.
- Preserve the original terminating signal after bounded cleanup instead
of converting it to an ordinary exit.
- Do not add an arbitrary operation timeout: a timed-out committed
mutation can still finish remotely and would make retry safety
ambiguous.

## Todo

- [x] Reproduce the reported fragment on CLI 0.290.0, current main, and
Node 26.0.0
- [x] Add structured termination reporting to `mcp single-op-call`
- [x] Cover the nested SVG style input and termination payload
- [x] Exercise the installed one-shot `beforeExit` lifecycle, not only
the payload formatter
- [x] Stop execution after asynchronous checkpoint preflight resumes
from termination
- [x] Review documentation impact — existing CLI docs already promise
structured JSON for failed one-shot calls; no content update is required

## Verification

- Nested-SVG reporter regression demonstrated fail-before
(`reportMcpSingleOpCallTermination is not a function`) and pass-after
- Installed-lifecycle regression demonstrated fail-before when the
active-call hook was disabled and pass-after when restored
- `pnpm --filter webstudio exec vitest run src/commands/mcp.test.ts` —
43 passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings packages/cli/src/commands/mcp.ts
packages/cli/src/commands/mcp.test.ts`
- `pnpm --filter webstudio exec vitest run src/bin.test.ts` — 3 passed
- `pnpm --filter webstudio test` — 998 passed; two unrelated
concurrent-suite timeouts (`src/bin.test.ts` and
`evaluations/high-impact/fixture-api.test.ts`). The launcher test passed
in isolation. The evaluation fixture was not rerun because evaluations
require separate approval.

## Known limitations

- SIGKILL cannot be intercepted, so no process can emit a final
diagnostic in that case.
- This change does not impose an internal operation deadline or cancel
in-flight mutations.
- A real authenticated reproduction is still needed to identify why the
original project-session call remained active.

Ref #6132
2026-08-19 00:23:45 +02:00
Oleg Isonen 0760d6cfec fix: upload videos from Builder and MCP (#6189)
## Summary

Video uploads failed because file detection returned `video` while the
canonical Asset schema accepted only `font | image | file`. The mismatch
was introduced when video detection was added in #5539; the recent JSON
work only exposed the adjacent contract gap and did not cause it.

This makes `video` a first-class `Asset["type"]` across Builder, SDK,
storage formatting, project bundles, HTTP uploads, and MCP. There is no
separate upload-only type and no conversion from `video` to `file`.

## Implementation

- Add the canonical `video` Asset variant with width/height metadata.
- Make Builder UI reservations and optimistic assets preserve `video`.
- Make MCP and HTTP upload schemas derive their enum from the canonical
SDK `assetType`.
- Preserve video metadata through S3 and filesystem upload paths.
- Include canonical video assets in project/canvas bundles.
- Keep legacy video records without dimension metadata readable as
generic `file` assets.
- Fall back to a labeled file preview when the browser cannot decode a
video format such as AVI; browser-supported formats continue to render
video thumbnails.
- Regenerate runtime operation contracts and sync fixtures from the PR
deployment.

## Contract protection

- Assert every detected upload category parses as a canonical Asset
type.
- Exercise Builder reservations for every value in `assetType.options`.
- Assert MCP's upload enum exactly equals `assetType.options`.
- Exercise direct HTTP video upload metadata and URL construction.
- Cover canonical video formatting, legacy fallback, preview assets, and
canvas serialization.
- Verify a video decode error replaces the blank media element with a
visible format-labeled fallback.
- Store canonical WebM and MP4 records with dimensions in the
`webstudio-features` fixture and verify generated runtime metadata.

## Checklist

- [x] Make video a canonical Asset type
- [x] Verify Builder UI upload flow
- [x] Verify MCP and direct HTTP upload contracts
- [x] Add fail/pass regression coverage for the broken paths
- [x] Add an Assets panel fallback for browser-unsupported video formats
- [x] Regenerate runtime contracts
- [x] Sync and build fixtures against the PR deployment
- [x] Run affected package tests, typechecks, lint, package-boundary,
generated API, and generated docs checks
- [x] Review documentation impact — existing Assets documentation
already lists supported video formats and upload behavior; no
user-facing documentation change is required

## Verification

- Builder: 2,543 tests passed
- Builder asset thumbnail regression: 14 tests passed
- Project build/MCP runtime: 2,111 tests passed
- CLI: 999 tests passed
- SDK: 516 tests passed
- Asset uploader: 199 tests passed
- Protocol: 92 tests passed
- HTTP client: 38 tests passed
- Typechecks passed for Builder, SDK, asset uploader, project build,
protocol, HTTP client, and CLI
- Lint and package-boundary checks passed
- Generated API and documentation checks passed
- Fixture sync completed against
`fix-video-up-be4d8a0.development.webstudio.is`
- All fixture builds passed after synchronization

Closes #6186
2026-08-19 00:21:11 +02:00
Oleg Isonen 5a57234c75 revert: Store Content Block content in MDX assets
Revert the accidental merge of #6125 so the implementation can continue in draft PR #6181.
2026-08-18 13:23:32 +01:00
Oleg Isonen 5ec3547d36 fix: Support JSON content files in Builder (#6168)
## Summary

- make the complete asset filename editable in Asset settings
- convert existing editable text assets when their extension changes
while preserving the asset ID
- accept every JSON root type and normalize JSON-compatible syntax into
formatted strict JSON
- expose the same optional extension change through MCP/API
`update-asset-content`
- initialize new JSON files and empty text-to-JSON conversions with a
valid empty object
- distinguish browser URL imports from JSON file uploads so JSON objects
with a root `url` field remain file content
- update the Assets and Content Engine documentation

## Root cause

Asset settings rendered the extension as a fixed suffix outside the name
input. The metadata rename mutation could only change the display
basename, while content revisions always inherited the current storage
extension. As a result, changing `.md` to `.json` was impossible in the
UI and unavailable through the shared MCP/API revision command.

Asset settings now edits the complete filename. Ordinary renames keep
using the metadata mutation. A text-extension change loads the current
content, validates it for the target format, and writes a new immutable
revision through the existing conflict-safe content endpoint. The
existing Acorn-backed `parseJsonExpression` utility accepts
JSON-compatible syntax without evaluating code and serializes it as
strict JSON. Objects, arrays, strings, numbers, booleans, and `null` are
all accepted.

## Checklist

- [x] Create JSON files by entering a `.json` filename
- [x] Edit the complete filename in Asset settings
- [x] Convert editable text-file extensions while preserving the asset
ID
- [x] Expose extension conversion through `update-asset-content`
- [x] Create JSON files and convert empty text files with a valid empty
object
- [x] Normalize safe JSON-compatible syntax to strict JSON
- [x] Accept objects, arrays, and scalar JSON roots
- [x] Reject executable or incomplete content
- [x] Preserve browser-uploaded JSON object shapes
- [x] Review and update authoritative Builder documentation
- [x] Add focused UI, revision, HTTP-client, CLI/MCP, protocol, and
upload regression tests
- [x] Run affected typechecks, lint, documentation checks, and the
Builder production build
- [ ] Manually verify create, edit, rename `.md` to `.json`, reload
persistence, and Content Engine query in a browser
- [ ] Run the complete local agent evaluation suite if approved

## Verification

- 217 focused tests passed across Builder, asset revisions, HTTP client,
CLI/MCP, and protocol
- Builder, asset-uploader, HTTP-client, protocol, CLI, and project-build
typechecks passed
- Builder production build passed
- MCP and Content Engine documentation checks passed
- `oxlint` on changed TypeScript files passed
- `git diff --check` passed
- Regression tests were demonstrated failing before the implementation
and passing afterward

A disposable E2E database and PostgREST stack bootstrapped successfully
in the earlier real-path test, but UI automation remained blocked
because no controllable browser was available. Fixture inputs and
generated fixture outputs are not affected. Agent evaluations were not
run because approval is required and was not requested.

Closes #6167
2026-08-18 11:39:01 +02:00
Oleg Isonen 81d5148046 fix: canonicalize trailing-slash page URLs (#6173)
## Outcome

Canonical page URLs now omit a trailing slash in Webstudio Cloud sites
and exported JavaScript applications. A request such as
`/about/?campaign=launch` receives a permanent `301` redirect to
`/about?campaign=launch`, preventing both URL variants from serving
identical page content.

The behavior is implemented once in the generated root redirect handler,
so it covers fixed and dynamic application routes without generating an
extra route per page. Exact project redirects remain authoritative.
Protocol-relative-looking paths such as `//example.com/` are left
untouched so canonicalization cannot become an external redirect.

Static exports cannot redirect requests themselves. Their URL behavior
remains host-specific and is documented for Cloudflare Pages, Netlify,
Vercel, and other static hosts.

## Implementation

- Add one shared pathname normalizer in the SDK.
- Apply canonicalization after exact authored redirect matching in both
generated application runtimes.
- Ignore repeated leading slashes before constructing a redirect
location.
- Extend redirect-loop detection to model the implicit canonical
redirect.
- Regenerate the affected fixture applications from their templates.
- Document application canonicalization and host-specific static export
behavior.

## Todo

1. [x] Add central trailing-slash canonicalization.
2. [x] Preserve query strings, encoded path segments, and the root path.
3. [x] Cover concrete dynamic URLs without per-route generation.
4. [x] Preserve explicit project redirect precedence.
5. [x] Detect redirect loops that pass through canonicalization.
6. [x] Prevent protocol-relative external redirects.
7. [x] Regenerate and review fixture output.
8. [x] Review documentation impact; document application and SSG
behavior.
9. [x] Run targeted and repository-level verification.

## Verification

- SDK tests: 548 passed
- SDK typecheck: passed
- Project Build tests: 2,379 passed
- Project Build typecheck: passed
- CLI tests: 997 passed; two tests timed out only while three package
suites ran concurrently, then both affected files passed in isolation
(6/6)
- Redirect regression suite after security follow-up: 302 passed
- CLI typecheck: passed
- `pnpm lint`: passed
- `pnpm check:package-boundaries`: passed
- `pnpm fixtures`: passed
- Documentation internal anchors and Markdown whitespace: checked
- New regression tests were confirmed failing before their
implementations and passing afterward
- Agent evaluations were not run because they require separate explicit
approval

Closes #6172
Closes https://github.com/webstudio-is/webstudio/issues/4327
2026-08-18 11:38:27 +02:00
Oleg Isonen 86b6310fff feat: Store Content Block content in MDX assets 2026-08-17 15:22:20 +01:00
Oleg Isonen af11ec211f feat: Search all local project data from MCP (#6165)
Ref #6154

## What

- Add standalone search across all 13 synchronized Builder namespaces.
- Search in the CLI ProjectSession and send only paged matches to the
model; initial synchronization is unchanged.
- Return stable match ids, exact paths, entities, references, and
affected routes.
- Preserve deprecated `scopes` output, redact recognized credential
fields, and document the command.

## Objectives

- [x] O1 — Universal Builder namespace search
- [x] O2 — Stable identity and actionable location
- [x] O3 — Filters, pagination, route impact, and references
- [x] O4 — Honest synchronization → local search → model-input boundary
- [x] O5 — Standalone command and workflow documentation
- [x] O6 — Safety, compatibility, typed contracts, and regression tests

Verified by Atlas (implementation), Scribe (docs/workflow), and Sentinel
(adversarial review).

## Remaining #6154 work

- [ ] Atomic semantic multi-record updates
- [ ] Targeted route rendering and assertions
- [ ] General slow-operation consent and time budgets
- [ ] Full agent evaluation suite after approval

## Verification

- Project search + MCP: 129 tests passed
- Protocol docs: 8 tests passed
- CLI schema/docs: 23 tests passed
- Compact MCP handshake budget test passed
- Project-build, protocol, and CLI typechecks passed
- Generated docs and API checks passed
- Agent evaluations not run; approval is required

## Limitation

Asset metadata is searchable. Asset binaries and document contents are
not Builder namespace data and are not searched.
2026-08-17 14:08:25 +02:00
Oleg Isonen 4ca695cf46 fix: Improve MCP screenshot and session compatibility (#6163)
## Summary

Allow responsive MCP screenshots to use local project data and remove
the CLI session storage dependency on the Web Crypto global.

## Root causes

- Responsive capture rejected every `source: "local"` request before
preview preparation, even though the generated preview and reusable
browser session support local data.
- Local screenshot inputs skipped collision-free port resolution.
- Project session revisions and restore-point IDs called
`crypto.randomUUID()` through the ambient global. Environments without
the Web Crypto global failed after otherwise successful mutations.
- The production/session route-generation failure from #6161 is no
longer reproducible on current `main`; the exact reported flow succeeds,
so this PR does not add a speculative change for it.

## Technical choices

- Resolve an available preview port for local generated screenshots
using the existing screenshot port path.
- Permit local and session sources through the same responsive
generated-preview capture path.
- Keep mixed preview targets and mixed browser configurations rejected.
- Import `randomUUID` from `node:crypto` for session revisions and
restore points.
- Preserve explicit URL and `baseUrl` capture behavior.

## Impact

A single `screenshot.responsive` call can prepare a local project once,
reuse the configured browser, and capture desktop, tablet, and mobile
viewports. Native MCP mutations can persist their session state without
requiring the Web Crypto global.

## Checklist

- [x] Add fail-first coverage for local responsive preview preparation
and port allocation
- [x] Add fail-first coverage for session persistence without the Web
Crypto global
- [x] Verify a real local CLI capture at 1440×900, 768×1024, and 390×844
- [x] Verify the production/session scenario from #6161 on current
`main`
- [x] Run the complete CLI test suite
- [x] Run CLI typecheck and lint
- [x] Review documentation impact: no user documentation changes are
required because this restores the documented MCP behavior
- [ ] Run agent evaluations (requires explicit approval)

## Verification

- `pnpm --filter webstudio test` — 62 files, 983 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts
packages/cli/src/project-session.ts
packages/cli/src/project-session.test.ts`
- `git diff --check`
- Real local-source production capture returned HTTP 200 at all three
viewports using one generated preview and the requested Chrome
executable

Closes #6156
Closes #6160
Ref #6161
2026-08-17 12:31:10 +02:00
Oleg Isonen baa48232fc fix: Keep Assets routes out of the static sitemap (#6158)
## Summary

- keep Assets-backed dynamic path discovery and SSG prerendering
unchanged
- stop merging Assets-derived paths into `$resources.sitemap.xml`
- keep eligible static pages in the generated sitemap
- update the existing Assets prerender integration test to cover static
sitemap behavior

## Why

The CLI used one Assets query result for both route generation and
sitemap indexing. Renderability and indexability are separate concerns,
and automatic insertion could duplicate entries from a custom
`/sitemap.xml` page. The Builder already exposes only static sitemap
data, so generated production output should match it.

## Compatibility and release note

Assets-backed dynamic routes are no longer added automatically to the
static sitemap. Existing projects that relied on that behavior need to
add those routes explicitly to a custom `/sitemap.xml` page with an
Assets resource and Collection. This `fix:` PR is included in the
repository's generated release notes under **Fixes**.

## Technical choices

- Removed only the Assets-derived sitemap collection and merge.
- Left `getAssetResourcePrerenderPaths()`, query evaluation, route
generation, prerendering, and draft filtering unchanged.
- Continued using `getStaticSiteMapXml()` as the sole source of
generated sitemap entries.

## Verification

- Regression demonstration before the implementation change: the updated
targeted test failed because `/blog/hello-world` was present in
`$resources.sitemap.xml.ts`.
- After the implementation change, the targeted integration test passed
and successfully built/prerendered `/blog/hello-world`.
- `pnpm --filter webstudio test -- src/prebuild.test.ts` — 58 tests
passed.
- `pnpm --filter webstudio typecheck` — passed.
- `pnpm exec oxfmt --check packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts` — passed.
- `pnpm exec oxlint --deny-warnings packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts` — passed.
- Agent evaluations were not run, per request.
- Fixture regeneration was not needed because no fixture inputs or
outputs changed.

## Todo

- [x] Invert the existing Assets prerender sitemap assertion.
- [x] Demonstrate that the regression test fails with the old behavior.
- [x] Remove Assets-derived sitemap collection and merging.
- [x] Confirm dynamic Assets routes still build and prerender.
- [x] Confirm drafts stay excluded and eligible static pages stay
included.
- [x] Review documentation impact.
- [x] Review the authoritative `docs/` content: the Content Engine guide
already directs users to build and draft-filter a custom sitemap for
dynamic Assets entries, and the XML Node guide documents combining it
with the static Sitemap resource; no docs change is required.
- [x] Document the compatibility change for generated release notes.

Closes #6157
2026-08-15 22:52:45 +02:00
Oleg Isonen dbfb0a9849 fix: Preserve variable font styles (#6153)
## Summary

Preserve font style metadata for variable fonts so upright and italic
files from the same family produce distinct `@font-face` rules. Variable
fonts with an `ital` or `slnt` axis are also registered for the matching
CSS styles.

## Technical choices

- Detect `normal`, `italic`, and `oblique` from the font binary,
preferring OS/2 `fsSelection`, then `post.italicAngle`, then the
existing subfamily parser.
- Add `style` to variable-font metadata while defaulting legacy stored
records to `normal`.
- Keep update metadata partial and strict, but accept and persist
`style` and `variationAxes` together so a `get-asset` result can be
written back.
- Group generated font faces by family, style, and browser-selectable
variable descriptors so separate upright and italic files cannot
overwrite each other.
- Regenerate the runtime operation contracts from the updated schema.

## Checklist

- [x] Add regressions using real upright and italic Inter variable-font
binaries.
- [x] Cover style detection, metadata compatibility, updates, and
generated font-face selection.
- [x] Verify the final stylesheet contains distinct upright and italic
`@font-face` rules.
- [x] Verify the public asset update contract accepts a complete
variable-font metadata object.
- [x] Review documentation impact and update the custom-font guide.
- [x] Regenerate and verify runtime API contracts.

## Verification

- `@webstudio-is/fonts`: 14 tests and typecheck passed.
- `@webstudio-is/asset-uploader`: 198 tests and typecheck passed.
- `@webstudio-is/sdk`: 514 tests and typecheck passed.
- `@webstudio-is/project-build`: 2,094 tests and typecheck passed.
- CLI: 980 tests and typecheck passed.
- Builder typecheck passed.
- `pnpm lint`, `pnpm check:package-boundaries`, `pnpm
check:generated-api`, and `pnpm check:generated-docs` passed.
- Regression tests were demonstrated failing against the previous
behavior and passing with the fix.
- Fixture builds regenerated successfully with no diff. Full remote
fixture sync was attempted, but the deployed API rejected the source CLI
contract as incompatible.
- Agent evaluations were not run because they require separate explicit
approval.

Closes #6150
2026-08-15 22:52:00 +02:00
Oleg Isonen 05118e0f22 fix: recover from browser startup failures (#6135)
## Summary

Recover screenshot and rendered-audit capture when a discovered Chromium
executable fails before its DevTools endpoint becomes ready.

The CLI now tries the next compatible installed browser for standalone
screenshots, reusable captures, and multi-viewport capture batches. When
no candidate starts, the error identifies every attempted executable and
its startup failure.

## Root cause

Browser discovery stopped after selecting the first executable that
existed on disk. If that executable aborted, failed to spawn, or never
exposed its DevTools endpoint, capture failed even when another
supported browser was installed.

Startup failures also crossed the Vision/CLI package boundary through an
untyped string code, and concurrent captures could duplicate failure
diagnostics.

## Technical choices

- Define the typed `BrowserStartupError` contract in Vision.
- Keep executable discovery and fallback ownership in CLI, where all
browser candidates are known.
- Share one fallback loop across standalone capture, reusable capture,
and viewport batches.
- Treat an explicit `browserPath` as strict and never launch a different
executable.
- Reuse the first successful browser session and deduplicate concurrent
startup failures by executable path.
- Preserve original startup causes while adding aggregate executable
diagnostics.

## Checklist

- [x] Classify browser exits, synchronous spawn failures, and DevTools
startup timeouts.
- [x] Fall back across compatible installed browsers.
- [x] Cover standalone, reusable, and multi-viewport capture paths.
- [x] Preserve strict explicit-browser behavior.
- [x] Aggregate all failed executable paths and causes.
- [x] Deduplicate concurrent failure diagnostics.
- [x] Demonstrate each regression failing before and passing after the
fix.
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected.
- [x] Review documentation impact: no user-facing documentation changes
are required.

## Verification

- `pnpm --filter webstudio test` — 62 files, 978 tests passed
- `pnpm --filter @webstudio-is/vision test` — 13 files, 79 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm --filter @webstudio-is/vision typecheck`
- `git diff --check`
- Independent subagent reproduction and post-fix harness verification
passed for standalone capture, reusable capture, viewport batches,
explicit paths, aggregate failures, session reuse, and concurrent
failures.

The full agent evaluation command was not run because it requires
separate approval.

## Known limitation

The exact nested SVG fragment from #6132 completes on current main and
the reported CLI release, so this PR does not claim to fix or close that
issue.

Closes #6133

Closes #6134

Ref #6132
2026-08-14 10:17:04 +02:00
Oleg Isonen 42f5291168 fix: Reconcile MCP commits and Cloudflare scaffold types (#6131)
## Summary

- confirm ambiguous project commits by retrying the exact transaction ID
before treating a version conflict as a rejection
- share the confirmation path between normal mutations and restore-point
commits
- emit an explicit sitemap entry type so an empty sitemap remains
type-safe
- generate and publish TypeScript declarations for
`@webstudio-is/wsauth`
- regenerate affected CLI fixtures

## Root cause

The project API already treats the latest transaction ID as idempotent,
but ProjectSession interpreted the first `CONFLICT` response as a
rejected write. If the original transaction had committed, the
subsequent refresh exposed the requested values while the tool still
reported failure.

The Cloudflare scaffold had two independent type-contract gaps: an empty
generated sitemap inferred `never[]`, and the wsauth declaration build
inherited a shared output directory, leaving its published package
without the declaration files referenced by its exports.

## Technical choices

- Retry only a version conflict, once, with the same base version and
transaction ID. A confirmed retry returns the committed server version;
persistent conflicts continue through the existing refresh/retry-safe
flow.
- Use the same confirmation helper for regular patch and restore-point
transports.
- Keep the generated sitemap module standalone by emitting its concrete
entry type inline.
- Make the wsauth declaration config extend its package config and
publish nested `lib/types` output.
- Preserve existing API payloads and server idempotency behavior; no
migration is required.

## Todo

- [x] Reproduce and cover ambiguous committed mutations
- [x] Reuse conflict confirmation for restore-point commits
- [x] Cover empty sitemap typechecking
- [x] Generate and inspect the wsauth package declarations
- [x] Regenerate affected fixtures
- [x] Verify the Cloudflare fixture typecheck and production build
- [x] Review documentation impact — no user-facing documentation changes
are required

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,092 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter webstudio test` — 972 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm --filter @webstudio-is/wsauth test`
- `pnpm --filter @webstudio-is/wsauth typecheck`
- `pnpm --filter @webstudio-is/wsauth build && pnpm --filter
@webstudio-is/wsauth dts`
- `npm pack --dry-run --json` in `packages/wsauth` — both public
declaration entrypoints included
- `pnpm -r fixtures:build`
- `pnpm --filter webstudio-cloudflare-template typecheck`
- `pnpm --filter webstudio-cloudflare-template build`
- focused formatting, lint, and `git diff --check`

Agent evaluations were not run because they require separate explicit
approval.

Closes #6129
Closes #6130
2026-08-13 23:07:18 +02:00
Oleg Isonen 146e65fe0d fix: Forward fragment token conflict resolution (#6122)
## Summary

- expose `conflictResolution` on both JSX fragment MCP tools
- derive the JSX-facing fragment contract from the Builder runtime
contract instead of maintaining a second handwritten field list
- preserve `conflictResolution`, `contentMode`, insertion mode, and
insertion index through JSX normalization
- verify all 151 MCP-exposed public APIs stay aligned with the generated
public API catalog
- keep the normal MCP handshake below its size budget while retaining
the complete schema in focused discovery
- cover direct and verified fragment insertion, shared-contract parity,
and reuse of the existing token id

## Implementation

The runtime remains the canonical semantic contract for fragment
insertion. The MCP adapter now inherits its fields and validation from
`insertFragmentInput`, overriding only the transport-specific
differences: MCP accepts `fragment` as a Webstudio JSX string and
requires `parentInstanceId`. After converting JSX to structured
Webstudio data, the adapter validates the result with the same runtime
schema used by Builder operations.

This removes the duplicated conflict-resolution, content-mode,
insertion-mode, and insertion-index definitions. It also restores
`contentMode` to the MCP schema, where the previous handwritten contract
had omitted it. Builder performance is unchanged because JSX parsing
exists only on the MCP boundary; Builder continues to pass structured
fragments directly to the runtime.

A catalog-wide audit confirmed that the other public API tools already
derive their schemas from the generated protocol catalog. There are 152
public APIs: 151 are exposed through MCP and `copy-page` is
intentionally hidden because its hydrated `Map` input is not
serializable. The only schema adapters are `insert-fragment` and
`insert-collection`, where MCP accepts JSX instead of structured
fragment data. A permanent test now compares complete semantic field
schemas and required fields for every exposed API, including types,
enums, constraints, descriptions, branched schemas, and focused schemas
that were compacted during the handshake. The collection adapter test
also covers all optional insertion controls.

The runtime already resolves colliding token names and reuses the target
token id when `conflictResolution` is supplied. The complete
`insert-fragment` input remains available through focused schema
discovery, while its normal handshake representation is compacted to
keep the complete tool handshake below the existing 200 KB limit.

No migrations or persistence-format changes are required. Existing calls
that omit the optional fields keep their current behavior.

## Checklist

- [x] Add `conflictResolution` to `insert-fragment`
- [x] Inherit the option in `insert-fragment-verified`
- [x] Derive shared MCP fields and validation from the runtime fragment
contract
- [x] Keep JSX parsing and required-parent validation at the MCP
boundary
- [x] Validate converted JSX with the runtime schema
- [x] Restore `contentMode` to the MCP contract
- [x] Audit all 152 public APIs for duplicate MCP contracts
- [x] Add catalog-wide complete-schema parity coverage for all 151
MCP-exposed APIs
- [x] Cover all `insert-collection` insertion controls through the JSX
adapter
- [x] Demonstrate the new contract and forwarding tests fail when
behavior is disabled and pass when restored
- [x] Keep the MCP handshake within its existing size budget
- [x] Verify `ours` attaches the existing token id without adding a
duplicate token
- [x] Regenerate and verify runtime API contracts
- [x] Run the full fixture sync/build; no fixture changes were produced
- [x] Review generated MCP documentation; no documentation output
changed
- [x] Review documentation impact; no user-facing documentation update
is required
- [ ] Run agent evaluations (not run because they require separate
explicit approval)

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,089 tests passed
- `pnpm --filter webstudio test` — 971 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint packages/cli/src/commands/mcp.test.ts
packages/project-build/src/mcp.test.ts`
- `pnpm exec oxfmt --check packages/cli/src/commands/mcp.test.ts
packages/project-build/src/mcp.test.ts`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`
- `pnpm fixtures`

Closes #6121
Ref #5928
2026-08-13 12:55:30 +02:00
Oleg Isonen 58eaf8c7c9 fix: Publish docs independently of release smoke (#6120)
## Summary

- finish the `publish` job after npm publication and exact
registry-artifact verification
- run packaged CLI verification in a separate `release-smoke` job
- keep `publish-docs` dependent on successful release creation and
package publication, but independent of post-publish smoke results
- verify the Codex registration command through `webstudio connect codex
--print` in CI instead of requiring an installed Codex client

## Root cause

The release workflow ran post-publish CLI smoke verification inside the
`publish` job. GitHub therefore marked package publication as failed
whenever the smoke failed, even though npm publication had already
completed. Because `publish-docs` declared `needs: [release, publish]`,
GitHub skipped documentation publication.

The recurring failure was the smoke invoking `webstudio connect codex`
on a runner without the `codex` executable.

## Technical choices and tradeoffs

Package publication remains strict: npm publication and exact registry
visibility must both succeed before downstream jobs run. Post-publish
smoke verification remains a red, visible release job and still uploads
its diagnostic artifact, but it no longer changes the truthful outcome
of publication or blocks docs.

The Codex package smoke now validates the generated registration command
without mutating a real Codex installation. The command subprocess
boundary remains covered by the existing focused connect-command tests.

No migrations or compatibility changes are involved.

## Todo

- [x] Separate post-publish CLI smoke from package publication.
- [x] Keep docs gated on successful release and registry publication.
- [x] Make Codex connection verification deterministic in CI.
- [x] Manually advance the `docs` branch to release 0.290.0.
- [x] Review documentation impact: no product documentation update is
required because this changes release automation only.
- [x] Verify formatting, workflow syntax and topology, CLI tests, lint,
and typechecking.
- [x] Run the non-model release smoke and record the remaining unrelated
failure.

## Verification

- `pnpm exec oxfmt --check .github/workflows/release.yml
packages/cli/scripts/release-smoke.ts`
- parsed `.github/workflows/release.yml` with Ruby Psych
- asserted the parsed workflow job graph with the repository's `yaml`
dependency
- `pnpm --filter webstudio exec vitest run
scripts/release-smoke-registry.test.ts src/commands/connect.test.ts` —
22 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint packages/cli/scripts/release-smoke.ts`
- `pnpm --filter webstudio test:release-smoke` — the corrected Codex
path passed; the smoke later reached `preview.start` and failed on an
unrelated current-main fixture error: `Project session is missing
preview data: assetFolders`

Fixture review: this change does not modify fixture inputs or generated
fixture outputs.

Closes #6119
2026-08-12 22:50:41 +02:00
Oleg Isonen 21ca567686 fix: Stop owned preview process trees (#6111)
## Summary

Supervise generated preview processes so the CLI terminates the complete
owned process tree when an MCP session disconnects, a command finishes,
or the owner receives a termination signal. Record owned PIDs and report
actionable locked-path recovery when cleanup still fails.

Closes #6019

## Technical choices

- Start generated previews through a dedicated supervisor process
connected to its CLI/MCP owner over IPC.
- Disconnect the supervisor to stop a preview; the supervisor owns all
descendant cleanup.
- Use Execa `killDescendants` for cross-platform process-tree
termination instead of maintaining separate Windows and POSIX kill
implementations.
- Force-stop the owned tree so a descendant that ignores SIGTERM cannot
survive its launcher.
- Record supervisor and preview PIDs beside the generated preview
directory.
- Include the supervisor in the CLI build output.

## Implementation checklist

- [x] Add owned preview process-tree supervision.
- [x] Dispose previews on MCP completion, failure, disconnect, and
signals.
- [x] Add scoped locked-path diagnostics and recovery instructions.
- [x] Add lifecycle and command regression coverage.
- [x] Run the CLI typecheck and broader test suite.
- [x] Review authoritative `docs/` content; no user-facing documentation
changes are needed.
- [ ] Run the complete agent evaluation suite after explicit approval.

## Verification

- Preview server and supervisor tests: 42 passed.
- Full CLI test suite: passed.
- CLI typecheck: passed.
- CLI production build: passed.
- Package-boundary check: passed.
- Targeted lint and formatting: passed.
- `git diff --check`: passed.
- The supervisor-disconnect regression was verified to fail without the
new behavior and pass with it restored.
- Agent evaluations were not run because repository policy requires
separate explicit approval.
2026-08-12 22:09:03 +02:00
Oleg Isonen 87968f40ff fix: Improve MCP authoring and preview reliability (#6105)
## Summary

Fix MCP reliability failures reported in ten issues against CLI 0.288.0
and 0.289.0:

- keep every advertised array input schema client-valid, including
compact and tuple schemas
- report a text asset update as successful when its persisted revision
and bytes confirm that the server committed despite a failed response
- preserve standard package import resolution in generated React Router
previews
- upload variable WOFF2 fonts when callers provide the documented static
font metadata shape
- explain unmatched JSX as a stateless fragment syntax error
- distinguish the `animation` component namespace from value helpers and
reject unsupported CSS at-rules with an actionable validation error
- forward optional session tools such as `download-asset` through
top-level CLI shortcuts
- isolate published generated-preview dependencies from parent
workspaces
- return structured screenshot failures when React Router production
builds fail

Sensitive visual regression checks also run automatically for
organization-authored fork PRs. External fork PRs remain gated by
`safe-to-deploy`.

## Root causes and implementation

The MCP handshake compactor retained an array's type but discarded its
`items` schema. Unconstrained and tuple arrays could also be advertised
without `items`. Schema normalization now adds `items: {}` whenever an
array omits it, while focused discovery retains complete constraints.
Catalog coverage rejects every missing array item, including tuple
schemas, and the handshake remains below its 200 KB budget.

Asset content updates previously treated any failed response as a failed
mutation, even when the server had already swapped the revision.
Recovery now reads the current revision, compares the persisted bytes,
and confirms the revision remains stable across the read. It returns the
updated asset only when all evidence agrees. Otherwise it returns
`ASSET_UPDATE_COMMIT_UNCERTAIN` with guidance not to retry blindly.
Definitive 4xx responses retain their original errors.

The generated React Router Vite config replaced the client and SSR
resolver condition lists without explicitly retaining the standard
`import` condition. Both lists now preserve `import`. The regression
test generates a dynamic route, stages the built React Router SDK as a
published dependency without the local `webstudio` source condition, and
runs the generated build.

Variable fonts are detected with `variationAxes`. Merging the documented
static `style` and `weight` upload metadata into that detected variable
shape made validation fail after the binary was read. Font override
validation and shape-aware merging now live in `@webstudio-is/fonts`.
The SDK delegates to that domain function, which validates the complete
override before applying compatible fields, retains the detected
static-or-variable shape, and keeps the detected format.

Fragment parsing is stateless. Unmatched opening and closing tags now
explain that every opening element must use the same closing tag or be
self-closing, and that refreshing the session cannot repair malformed
JSX.

The `animation` binding is a component namespace, not a callable helper.
Tool schema text, evaluation errors, and the MCP manual now separate
component namespaces from value helpers. The MCP JSX `css` binding
parses the final interpolated CSS with CSS-tree and recursively rejects
selectors and unsupported at-rules such as `@keyframes`, while
preserving supported declarations and case-insensitive `@media` rules.
Keeping this validation at the external binding avoids changing internal
component templates.

CLI shortcut discovery previously omitted optional session tools even
though the runtime registered their handlers. Shortcut discovery now
includes the same optional tool categories as the CLI MCP host, while
real top-level CLI commands keep precedence.

Published generated previews could reuse a stale `node_modules` symlink
into a parent source workspace. Only development manifests containing
the local placeholder version may now reuse the CLI dependency tree.
Published manifests replace symlinks with a local install and disable
npm workspace discovery.

React Router could report a bundler failure while its build command
exited successfully outside CI, allowing screenshot startup to terminate
without a structured result. Managed production builds now set `CI=1`,
so the existing MCP error boundary receives the nonzero build failure
and prints structured JSON.

Visual regression authorization now distinguishes repository members
from external contributors. Organization members' fork PRs run on open,
synchronize, and reopen events; external fork code runs only from a
`safe-to-deploy` label event, and a later commit does not reuse that
approval.

## Implementation checklist

- [x] Preserve valid array item schemas in compact MCP discovery
- [x] Normalize tuple input schemas with an `items` fallback
- [x] Keep the MCP handshake below its 200 KB size budget
- [x] Cover the real `update-props` schema and oversized array
compaction
- [x] Reconcile failed asset-update responses against stable persisted
content
- [x] Reject recovery when a concurrent asset revision is detected
- [x] Preserve `import` resolution in generated React Router client and
SSR builds
- [x] Build a generated dynamic route against a staged published SDK
package
- [x] Regenerate and verify affected React Router fixtures
- [x] Preserve detected variable-font axes with documented upload
metadata
- [x] Add actionable unmatched-tag and unsupported-CSS validation
- [x] Correct animation namespace guidance in tool discovery and errors
- [x] Forward optional session tools through top-level CLI shortcuts
- [x] Replace parent-workspace dependency links for published previews
- [x] Disable npm workspace discovery for isolated preview installs
- [x] Make React Router build failures return nonzero in managed
previews
- [x] Run visual regression automatically for organization-authored PRs
- [x] Verify each regression test fails without its corresponding fix
- [x] Review documentation impact and update the authoritative MCP
manual plus generated outputs
- [x] Confirm no database migration or compatibility migration is
required
- [ ] Run `pnpm evaluations` after explicit approval; not run because
repository policy requires approval before agent evaluations

## Verification

- `packages/cli/src/prebuild.test.ts`: 57 tests passed
- `packages/cli/src/commands/schema.test.ts` and `mcp.test.ts`: 51 tests
passed
- `packages/project-build/src/runtime/components.test.ts` and
`mcp.test.ts`: 194 tests passed
- `packages/asset-uploader`: 196 tests passed
- focused font schema, SDK asset schema, CSS parser, and template CSS
tests passed
- `packages/http-client/src/index.test.ts`: 38 tests passed
- relevant CLI, project-build, asset-uploader, fonts, SDK, CSS data,
template, HTTP client, and React Router SDK typechecks passed
- MCP documentation tests, typecheck, generation, and Markdown source
tests passed
- React Router SDK package build passed
- full fixture link, sync, and generation pipeline passed
- affected Docker and Vercel fixture typechecks and locally bootstrapped
production builds passed
- complete CLI suite: 61 test files and 966 tests passed
- CLI typecheck passed
- preserved nested-workspace dynamic preview installed local `0.289.0`
dependencies and completed its production build
- visual-regression workflow YAML parsing and authorization truth table
passed
- repository lint passed with warnings denied
- formatting and `git diff --check` passed

## Tradeoffs and limitations

Deferred array schemas advertise unconstrained `items` in the compact
handshake to remain client-valid without duplicating detailed
constraints. Agents can retrieve the complete item schema through
focused `meta.get-more-tools` discovery.

Asset reconciliation performs three read requests only after an
uncertain 5xx, malformed response, or transport failure. This keeps the
normal successful path unchanged and avoids claiming success if the
revision changes during recovery.

For variable fonts, detected axes define the metadata shape. Static
`style` and `weight` hints are ignored because they cannot be
represented alongside `variationAxes`; compatible fields such as
`family` still override detection.

Native CSS keyframe authoring remains unsupported in fragment `css`
templates. The tool now rejects it before rendering and directs callers
to editable Webstudio Animation components instead of advertising a
nonexistent callable helper.

Published previews perform one isolated npm install when no valid local
dependency tree is cached. Local source development keeps the fast
workspace symlink path for placeholder-version manifests.

Closes #6104
Closes #6099
Closes #6098
Closes #6106
Closes #6107
Closes #6108
Closes #6114
Closes #6115
Closes #6116
Closes #6117
2026-08-12 21:23:35 +02:00
Oleg Isonen db2d092268 ci: replace Lost Pixel with self-hosted visual regression (#6086)
## Summary

Replace the defunct Lost Pixel service and the interim Chromatic
workflow with a fast, self-hosted visual comparison pipeline.

The workflow indexes the existing CSF stories without building
Storybook, builds and serves a lightweight browser app with Vite,
renders the merge base and current revision through Webstudio's shared
browser tooling, and compares screenshots with Webstudio's pixel-diff
and OCR engine. It uses GitHub Actions cache and artifacts only—no R2
bucket or external visual-testing service.

Closes #6064

## Implementation

- [x] Remove the Lost Pixel workflow, configuration, acknowledgement,
and global two-second wait.
- [x] Remove the interim Chromatic workflow and settings.
- [x] Extract source-agnostic capture, diff, OCR, audit, cache, and
report tooling into `@webstudio-is/vision`; rename the
Webstudio-specific preview boundary to
`@webstudio-is/project-build/preview`.
- [x] Expose the reusable browser capture session through the shared
vision boundary.
- [x] Make OCR and diff-artifact generation conditional on detected
pixel changes.
- [x] Render existing CSF stories through a typed, lightweight browser
harness built with Vite.
- [x] Replace the embedded HTML and handwritten HTTP/MIME server with a
real HTML entry, Vite production build, and Vite preview server; isolate
declarative build configuration in `story-build.ts`.
- [x] Apply Storybook preview annotations, decorators, global CSS,
backgrounds, and story metadata.
- [x] Store story scopes as shared data and load each revision's own
configuration.
- [x] Discover changed, added, and removed stories from both revisions.
- [x] Populate private story sources symmetrically in the baseline
worktree.
- [x] Isolate every story with a fresh React root and deterministic
time, randomness, intervals, focus, animation, and layout readiness.
- [x] Preserve story-owned visual options for masking, settling, and
interval control.
- [x] Reuse Vite preview servers, one Chromium process, grouped browser
pages, production bundles, and a bounded CPU worker pool for image
comparison.
- [x] Recheck initial pixel differences with serial captures, then
recapture persistent baseline/current pairs on the same browser page so
renderer variance cannot create false failures.
- [x] Report individual story render failures without discarding
successful captures.
- [x] Enforce a 0.1 per-pixel threshold and 0.001% total mismatch
tolerance.
- [x] Run OCR only for changed comparisons and include text-change
evidence.
- [x] Move the generic report model, writer, static viewer, and opener
into the shared Vision package for reuse by CI and future CLI
comparisons.
- [x] Bundle report HTML, styling, and browser behavior as static Vision
assets without runtime source-file lookups.
- [x] Copy external screenshot inputs into self-contained report
artifacts for CLI reuse.
- [x] Handle cached removed-only comparisons without rendering the
removed story on the current server.
- [x] Support focused local reproduction and opening the report with one
command.
- [x] Pin CI to the lockfile's Playwright Chromium, cache complete
revision screenshots with matching restore/save paths and automatically
derived runtime invalidation, and render both revisions on a miss.
- [x] Keep cache archives bounded to one revision instead of
accumulating the cache history.
- [x] Run the complete visual suite in one CI job and upload one report
as a 14-day artifact.
- [x] Fail PR checks on visual differences, empty runs, missing
screenshots, missing or empty report artifacts, and infrastructure
failures.
- [x] Rerun fork checks when `safe-to-deploy` is added.
- [x] Allow reviewed visual-only differences through
`visual-change-approved` and invalidate approval after new commits.
- [x] Keep capture, pixel diff, OCR, and report generation
source-agnostic for future draft-versus-published project comparisons.
- [x] Use Vite for the complete story HTML, module, asset,
production-build, and preview-server lifecycle; inline the selected
stories and skip ineffective tree-shaking to keep the cached run under
one minute.
- [x] Review Builder product documentation impact: no user-facing
Builder documentation is affected; contributor usage is documented in
`scripts/visual-regression/README.md`.
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected.

## Verification

- [x] Project-build Vision tests, including the reusable report test,
and entrypoint contract tests pass.
- [x] CLI screenshot, preview, and browser-session tests pass.
- [x] Project-build, CLI, Builder, design-system, and visual-runner
typechecks pass.
- [x] Visual runner tests pass (10), and the complete Vision package
passes (79), including demonstrated fail-before/pass-after coverage.
- [x] Full Storybook production build passes.
- [x] Full local Vite run completes all 238 stories in 69.9s cold and
50.3s with a cached baseline plus two same-page serial verifications,
with zero differences; identical-image comparison remains sub-second.
- [x] The report writer passes a bundled-output smoke test and
materializes external screenshot assets.
- [x] The generated report validates all 238 entries and referenced
images.
- [x] Hosted cached-baseline comparison completes all 238 stories in
52.9s with zero differences; the cache-miss fallback completes in
101.3s.
- [x] Cache-miss fallback renders both Git revisions on demand.
- [x] Visual differences produce baseline, current, diff, OCR, JSON, and
HTML evidence and fail without approval.
- [x] Hosted single-job visual workflow restores both the pinned browser
and baseline cache, validates all 238 public and private stories with
zero differences, and uploads the report.
- [x] The final Vite production build renders all 238 stories without
dependency invalidation or duplicate React runtimes, and restores
`NODE_ENV` so cached serial fallback installs the baseline's dev
dependencies.
- [x] Root lint, package-boundary validation, and relevant formatting
checks pass.
- [x] Workflow YAML parses successfully.
- [x] `git diff --check`, staged-diff review, and final worktree review
pass.

## Usage

Run the complete local comparison and open the visual report:

```sh
pnpm visual-regression:ui
```

Focus one component while iterating:

```sh
pnpm visual-regression:ui -- --grep "Design system/Button"
```

Reopen the latest report without rerunning:

```sh
pnpm visual-regression:report
```

The CI report is available as the `visual-regression-report` artifact.

## Tradeoffs

- Baseline screenshots are a disposable acceleration cache, not source
of truth. A missing or invalid cache renders the merge base directly
from Git.
- GitHub artifact storage provides downloadable one-click reports;
permanently hosted browser links would require another hosting surface.
- OCR runs after pixel triage because invoking Tesseract for every
unchanged story would make the common path unnecessarily slow.
- Vite production builds add Rollup's module graph and HTML/asset
pipeline. The runner emits one bundle because every selected story is
rendered, and skips tree-shaking because it removed little while
increasing build time.
- The verified local path is 50.3s cached and 69.9s cold. The hosted
path is 52.9s cached and 101.3s cold. A new browser/runtime cache must
render its baseline once; the pinned browser keeps later runners on the
reusable path.
- The complete suite stays in one Actions job; browser-page concurrency
is internal and bounded.
2026-08-12 10:55:52 +02:00
Oleg Isonen 5c6ffd3148 docs: document Content Engine workflows (#6097)
## Summary

- add a step-by-step Content Engine guide for building a Markdown
article overview and dynamic article page
- clarify that the example folder structure is optional and link to the
Markdown Blog marketplace template
- explain that an AI agent can complete the entire workflow through
Webstudio MCP and include a ready-to-use prompt
- document frontmatter, JSON files, relative and published asset paths,
document references, draft filtering, and single-result resources
- document Markdown-to-Markdown, Markdown-to-JSON, JSON-to-Markdown, and
JSON-to-JSON document references, including fragments and JSON Pointer
escaping
- add a schema-aware `webstudio man content-engine` reference for
fields, operators, saved and preview inputs, result/output/content
modes, diagnostics, limits, and reference syntax
- generate the GitBook Content Engine reference and the matching MCP
manual section from that shared CLI source
- fail documentation generation when result modes, output modes, content
modes, diagnostic fields, or Content Engine limits drift from the
implementation
- add four focused visual-editor screenshots
- keep the Assets page focused on asset management and link it to the
dedicated Content Engine guide
- update MCP Assets examples and the Markdown blog recipe to use
Markdown files, `result: "one"`, direct detail bindings, page metadata,
and visual verification

## Impact

Users get one workflow for treating files as the source of truth while
designing article listings and detail pages in Webstudio. The generated
reference documents the complete Assets query contract and
document-reference system. Agents receive the same reference through the
MCP manual, so the product and agent documentation no longer need
separate factual copies. This does not change product runtime behavior.

## Checklist

- [x] Documentation impact reviewed
- [x] Added the Content Engine guide and generated reference to
`docs/SUMMARY.md`
- [x] Updated related Assets, Data variables, and MCP documentation
- [x] Verified internal documentation and image targets
- [x] Verified the Markdown Blog marketplace URL returns HTTP 200
- [x] Confirmed the MCP recipe test failed before the recipe correction
and passed afterward
- [x] Confirmed the schema-coverage test fails when an implemented
result mode is omitted and passes after restoration
- [x] Confirmed both Content Engine generator tests fail without their
corresponding generator behavior and pass after restoration
- [x] Ran `pnpm --filter @webstudio-is/project-build test --
src/mcp.test.ts`
- [x] Ran `pnpm --filter webstudio test --
src/commands/api-command-docs.test.ts`
- [x] Ran the Content Engine and MCP generator tests
- [x] Ran project-build and CLI typechecks
- [x] Ran `pnpm check:package-boundaries`
- [x] Ran targeted lint and formatting checks
- [x] Ran `pnpm check:generated-docs`
- [x] Verified the final diff and working tree
- [ ] Agent evaluation suite: not run because it requires explicit
approval

Ref #5973
2026-08-11 21:24:27 +02:00
Oleg Isonen 93fa799cb8 fix: Preserve SSG exports with Assets queries (#6102)
## What changed

- Preserve the SSG asset-resource template as a package-relative file
URL in the bundled CLI.
- Ignore Assets queries that do not bind any parameters from the current
dynamic SSG route, while retaining the error for partial or split
multi-parameter bindings.
- Recognize the persisted `$ws$system.params.*` expression form when
enumerating dynamic SSG paths.
- Add a required package smoke test that deploys the release-shaped SSG
package, runs the packaged CLI, runs Vite and Vike, and verifies
rendered Assets data.

## Why

Two gaps caused the staging export failures:

1. Vite inlined the template URL as a `data:` URL, which Node could not
pass to `readFile`.
2. Dynamic-route validation treated an unrelated Assets query as an
incomplete route enumerator.

The initial smoke test only checked that an HTML file existed and could
pass while resource loading logged a `400`. The replacement test
verifies the rendered query result and fails on resource-loading errors.

## Technical choices

- The package smoke uses `createPublishedProjectBundleFixture`, so
fixture data stays checked against the published bundle contract.
- Persisted expressions use `encodeDataSourceVariable`; the generated
query now receives the real route parameter instead of compiling to
`undefined`.
- Temporary deployment, cleanup, subprocess execution, and assertions
live in `test:ssg-package`. The workflow invokes one repository command.
- Asset downloads are disabled to keep the smoke deterministic. It still
covers CLI packaging, the embedded Assets index, dynamic path
enumeration, generated resource execution, Vite compilation, and Vike
prerender.
- The test asserts the queried title in `/posts/hello-world`, rejects
any resource-load error, and verifies that an unrelated query does not
invent a dynamic path.

## Todo

- [x] Reproduce the invalid URL scheme through a bundled template
reader.
- [x] Preserve the package-relative template URL in the production CLI
bundle.
- [x] Ignore Assets queries unrelated to the current dynamic route.
- [x] Preserve rejection of incomplete multi-parameter route queries.
- [x] Support persisted system-variable expressions during route
enumeration.
- [x] Add a production-shaped packaged CLI and Vike smoke test.
- [x] Make the packaged SSG smoke test required on `main`.
- [x] Run CLI tests, typecheck, lint, build, and the packaged smoke
test.
- [x] Rerun the previously failing `WEBSTUDIO-LANDING` SaaS SSG
workflow.
- [x] Review documentation impact; no user-facing documentation changes
are needed.
- [x] Review fixture impact; synced fixture inputs and generated outputs
are unaffected because smoke data exists only in a temporary deployment.

## Verification

- `pnpm --filter webstudio test` — 61 files, 977 tests passed before the
final test addition
- `pnpm --filter webstudio test -- prebuild.test.ts
ssg-asset-resource-fetch-template.test.ts` — 57 tests passed
- `pnpm --filter webstudio test:ssg-package`
- `pnpm --filter webstudio typecheck`
- `pnpm lint`
- `pnpm --filter webstudio build`
- Package regression proof: the smoke failed on the original
unrelated-query implementation with `Dynamic SSG route parameters must
be completely enumerated by one Assets query`, then passed with the fix
restored.
- Persisted-expression regression proof: the focused test returned no
prerender path before encoded-system support, then passed with the
support restored.
- Template regression proof: the focused bundle test failed with
`ERR_INVALID_URL_SCHEME` before the fix and passed after it.
- Exact SaaS E2E rerun passed: project sync, CLI build, Vike build,
archive upload, and database update ([run
31519515809](https://github.com/webstudio-is/webstudio-saas/actions/runs/31519515809)).

Closes #6100

Supersedes #6101 after moving the change to the staging branch.
2026-08-11 20:56:12 +02:00
Oleg Isonen eb46abf4e5 fix: Recover missing preview assets (#6091)
## Summary

Fix session-based generated preview startup when the local
`.webstudio/assets` directory has not been created yet.

- materialize the current session's asset files before compiling the
Assets resource index
- reuse the existing safe asset downloader so missing parent directories
are created and downloads retain retry and temporary-file cleanup
behavior
- return the stable `PREVIEW_ASSET_DOWNLOAD_FAILED` code with a concrete
recovery action when asset access cannot be restored automatically
- document the automatic recovery and regenerate the authoritative MCP
manual

## Root cause

`preview.start` materialized current ProjectSession data and compiled
reachable Assets resources before generated-app prebuild. That
compilation called `realpath` on the local assets directory, but the
session path had never downloaded assets the way `webstudio sync` does.
A newly linked project without `.webstudio/assets` therefore failed with
`ENOENT` before preview generation.

## Technical choices

- Download only after confirming that the project has a reachable Assets
resource compilation plan.
- Keep canonical path and symlink validation in the filesystem content
source after materialization.
- Wrap download failures at the preview boundary without exposing local
paths or raw remote diagnostics.
- Preserve the existing generated-doc source/generator workflow rather
than editing generated guidance alone.

## Todo

- [x] Reproduce startup from a missing local assets directory
- [x] Materialize required session assets before index compilation
- [x] Add actionable failure recovery
- [x] Add fail/pass regression coverage
- [x] Review documentation impact and update the MCP vision guidance
- [x] Regenerate the CLI and GitBook MCP manuals
- [x] Review fixture impact — no fixture inputs or generated fixture
outputs are affected
- [x] Run the focused low-reasoning Markdown fixture, including the cold
assets-directory scenario
- [ ] Deferred: run the remaining agent evaluation fixtures

## Verification

- Regression demonstrated failing before the fix with `ENOENT` for the
missing assets directory, then passing after the fix.
- Recovery regression demonstrated failing with raw `ENOENT`, then
passing with `PREVIEW_ASSET_DOWNLOAD_FAILED` and the retry action.
- `pnpm --filter webstudio test` — 60 files, 976 tests passed.
- `pnpm --filter webstudio exec vitest run src/project-session.test.ts
src/mcp-guidance.test.ts` — 25 tests passed after final fixture cleanup.
- `pnpm --filter webstudio typecheck` — passed.
- Targeted `oxlint --deny-warnings` — passed.
- Documentation generation, docs tests, and docs typecheck — passed. The
aggregate generated-doc clean-tree guard reports the intentionally
changed generated CLI/GitBook outputs included in this PR.
- `git diff --check` — passed.
- Focused real-world evaluation: `gpt-5.4-mini` with
`model_reasoning_effort="low"`, `markdown-blog-v1`, 12/12 MCP calls
succeeded, 0 failures, 0 retries, 2 rendered verifications, and all
fixture checks passed.
- Cold-directory variant: removed `.webstudio/assets` immediately before
the first rendered verification; preview restored the required assets,
and both verification calls plus content-database checks passed.
- Remaining high-impact fixtures were not run; this was the targeted
real-world scenario requested for #6090.

## Known limitations

Preview startup still requires network and project asset access when
required files are not already local. It now reports a stable error and
tells the caller to restore access and retry instead of leaking an
`ENOENT`.

Closes #6090
2026-08-10 21:13:45 +02:00
Oleg Isonen 63fcc9c5a6 fix: Improve MCP preview verification reliability (#6084)
## Summary

Fix the third MCP reliability batch across generated catch-all routes,
rendered screenshot evidence, and preview startup diagnostics.

## Root causes

- Incremental prebuild rewrote an authored `/*` route with the
redirect-only fallback at `routes/$.tsx`.
- Screenshot capture trusted the browser writer result without verifying
that the output artifact was readable and non-empty.
- Generated preview dependency installation had no timeout, and
preparation exposed only one broad progress phase.

## Implementation

- [x] Preserve authored catch-all HTML routes during full and
incremental React Router generation while retaining the redirect
fallback when no catch-all page exists.
- [x] Probe every single and responsive screenshot artifact before
returning success; reject missing, unreadable, or zero-byte output with
stable error codes.
- [x] Bound generated preview dependency installation to two minutes.
- [x] Report session materialization, route generation, dependency
preparation, and completion phases through MCP progress output.
- [x] Deduplicate preview dependency preparation and MCP progress
wiring, flatten generation control flow, and consolidate overlapping
test setup.
- [x] Add regression coverage and demonstrate each new test failing
before its implementation and passing afterward.
- [x] Review documentation impact: no documentation update is required
because the existing preview and audit workflow is unchanged; this PR
makes its generated output and failure behavior reliable.
- [x] Review fixture impact and run the full fixture pipeline; no
fixture diff was produced.
- [ ] Agent evaluations — not run because repository policy requires
separate explicit approval.

## Verification

- `pnpm --filter webstudio test` — 60 files, 974 tests passed.
- `pnpm --filter webstudio typecheck`
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`
- `pnpm fixtures`

Closes #6081
Closes #6080
Closes #6078
2026-08-10 11:33:25 +02:00
Oleg Isonen dd316c53cc fix: Complete interrupted preview workflows (#6076)
## Summary

`webstudio mcp run` now emits a terminal structured error when the
process is interrupted while an MCP call such as `preview.start` is
still active. It preserves completed call results, identifies the
unfinished call and terminating signal, attempts preview cleanup, and
then propagates the original signal.

The MCP runner owns process termination for this workflow so the preview
controller cannot race it or propagate the same signal twice. Cleanup is
bounded to five seconds so a stalled preview startup cannot prevent
termination.

## Todo

- [x] Reproduce the missing terminal result for an interrupted
`preview.start` call
- [x] Report the active call and original signal in structured output
- [x] Preserve completed results from the shared MCP session
- [x] Clean up preview resources before propagating the signal
- [x] Prevent competing MCP-run and preview-controller signal handlers
- [x] Add regression coverage and verify it fails before the fix and
passes after it
- [x] Review for DRY violations and unnecessary abstractions
- [x] Review documentation impact; existing CLI documentation already
describes the restored terminal JSON behavior
- [x] Review fixture impact; no fixture inputs or generated fixture
bundles are affected
- [x] Run CLI tests, typecheck, lint, package-boundary checks, generated
API checks, and generated docs checks
- [x] Verify CI; all checks pass or are intentionally skipped except the
requested Lost Pixel exemption
- [ ] Run the complete local agent evaluation suite (requires explicit
user approval)

## Verification

- `pnpm --filter webstudio test` — 60 files, 972 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`
- `pnpm exec oxfmt --check packages/cli/src/commands/mcp.ts
packages/cli/src/commands/mcp.test.ts packages/cli/src/preview-server.ts
packages/cli/src/preview-server.test.ts`
- `git diff --check`
- GitHub CI — all checks passed or skipped except Lost Pixel, which is
explicitly excluded

Closes #6075
2026-08-09 10:14:48 +02:00
Oleg Isonen e4983b0d1d fix: Report unsupported Node versions (#6058)
Closes #6057

## Outcome

Generated projects now reject Node.js versions older than 22.12 before
React Router, Remix, or Vite evaluates SDK components. Users see the
package-manager engine error with the current and required versions
instead of the indirect `sanitize-html` / `htmlparser2`
`ERR_REQUIRE_ESM` stack trace.

## Root cause

`sanitize-html@2.17.6` synchronously loads the ESM-only
`htmlparser2@12`. That dependency combination relies on synchronous
`require(ESM)` support, which Node.js enabled by default in 22.12.
Generated projects incorrectly declared the broader `>=22` range,
allowing an unsupported runtime to reach SSR evaluation.

## Implementation

- Set the CLI and all generated base templates to `engines.node:
>=22.12.0`.
- Add npm `devEngines.runtime` with `onFail: error`, which runs before
install, CI, and run commands.
- Enable `engine-strict` in generated npm configuration so pnpm also
rejects an unsupported runtime.
- Regenerate every tracked CLI fixture package manifest.
- Update CLI documentation and the release-smoke manifest contract.
- Remove the earlier sanitizer-bundling workaround from the branch;
sanitizer code and packaging are unchanged from `main`.

## Checklist

- [x] Declare the exact Node.js compatibility floor
- [x] Fail before generated development, build, and start commands
- [x] Cover the generated engine contract with a fail/pass regression
- [x] Regenerate and inspect all affected fixtures
- [x] Review documentation impact and update the CLI guide
- [x] Keep sanitizer behavior and SDK packaging unchanged

## Verification

- Regression demonstrated failing before the template metadata change
and passing afterward
- `pnpm --filter webstudio test` — 946 passed
- `pnpm --filter webstudio typecheck`
- `pnpm --filter webstudio build`
- `pnpm fixtures`
- `pnpm lint`
- `pnpm exec oxfmt --check` on changed source, template, fixture, and
documentation files
- `git diff --check`

Agent evaluations were not run because they require separate approval.
2026-08-09 01:53:31 +02:00
Oleg Isonen 1fe087ed26 fix: Improve MCP preview and mutation reliability (#6073)
## Summary

Fix the second MCP reliability batch across tool discovery, runtime
mutations, preview/screenshot setup, Windows process launching, and
stdio recovery.

## Implementation

- [x] #6071 Accept canonical dotted/kebab tool names and MCP underscore
names in `mcp run`; return near matches and `meta.index` guidance for
unknown tools.
- [x] #6067 Parse JSON-stringified `tools` arrays in
`meta.get-more-tools`.
- [x] #6069 Preserve resource-backed variables when renaming without a
replacement value.
- [x] #6068 Parse CSS declaration strings consistently in design-token
style updates.
- [x] #6065 Fail immediately for occupied explicit screenshot ports and
discover Chromium from the Playwright browser cache.
- [x] #6066 Recover the stdio MCP transport after an abandoned partial
newline-delimited frame.
- [x] #6072 Launch managed Windows previews through Node plus
`npm-cli.js`, avoiding `spawn EINVAL`.
- [x] #6061 Reuse `npm-cli.js` when launched through `npx`, fall back to
the Node-adjacent npm CLI on Windows, forward `npm_config_cache`, and
include sanitized package-manager diagnostics.
- [x] Address review findings: preserve rebinding on resource scope
changes and discover default Playwright caches on Linux, macOS, and
Windows.
- [x] Complete the DRY pass: remove the dead npm command selector,
centralize accepted MCP tool-name lookup, simplify stdio frame recovery,
and share Levenshtein distance across MCP suggestions and screenshot
text matching.
- [x] Complete the follow-up review: centralize preview URL port
resolution, use `get-port` instead of custom TCP probing, use named
resolver dependencies, and correctly reuse owned previews on default
HTTP/HTTPS ports.
- [x] Complete the final review: use non-reserving `detect-port` checks
for explicit ports and repair the generated-app guidance regression
coverage.
- [x] Audit new utility code against maintained libraries: use
`fastest-levenshtein` for edit distance; retain custom stdio recovery,
Playwright cache discovery, and npm launching where no supported library
API preserves the required behavior.
- [x] Audit Windows/macOS/Linux compatibility helpers: use `path-key`
for environment PATH casing and `which` for executable discovery; retain
explicit Playwright layouts, browser application paths, npm CLI
resolution, symlink types, and process-tree shutdown where behavior is
platform- or product-specific.
- [x] Add fail/pass regression coverage for every fix.
- [x] Review documentation impact and update the CLI MCP sources plus
generated GitBook output.
- [x] Review fixture impact: no fixture inputs or generated fixture
bundles are affected.
- [x] Run targeted tests, package type checks, lint, package-boundary
checks, generated API checks, and generated documentation checks.
- [ ] Agent evaluations — not run because repository policy requires
separate explicit approval.

## Verification

- Complete CLI suite: 60 files, 968 tests passed.
- Project-build affected tests: 4 files, 382 tests passed.
- `pnpm --filter webstudio typecheck`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`

Each new regression test was run against the unfixed behavior to confirm
the expected failure, then rerun after its fix.

## Compatibility note

The stdio recovery layer discards only an unterminated frame that
remains idle for five seconds. Complete newline-delimited JSON-RPC
frames continue through the SDK transport unchanged.

Closes #6072
Closes #6071
Closes #6069
Closes #6065
Closes #6066
Closes #6067
Closes #6068
Closes #6061
2026-08-09 01:04:01 +02:00
Oleg Isonen d5b3f3ab80 fix: Improve MCP authoring reliability (#6056)
## Summary

This consolidates the reported MCP reliability fixes into one delivery.

- validate all inserted fragment expressions with the shared runtime
rules and reject text/expression children when component content models
require elements
- return an actionable error for unsupported existing-resource
references in JSX
- preserve siblings rendered before expression children
- accept unresolved CSS-variable shorthand expansion and eliminate
dynamic-alt/`htmlFor` audit false positives
- resolve exposed underscore MCP names during focused discovery
- omit unavailable stopped-preview fields and restart stale iterative
previews for external clients
- bound single and responsive screenshot capture, return
`SCREENSHOT_TIMEOUT`, and discard failed reusable browser sessions
- dispose previews when shared MCP runs fail or stdio clients
disconnect, including complete Windows process-tree cleanup
- resolve generated route templates from the CLI-managed preview
workspace during responsive verification
- retry a browser that exits during rendered-audit startup and report
sanitized exit diagnostics with a concrete browser-path recovery action
- materialize complete current session data before preview generation so
route order and project styles cannot collapse from missing lazy
namespaces
- always include CLI and Node.js version fields in generated issue
reports, with explicit legacy-client fallbacks
- document the updated preview and screenshot contracts and regenerate
the CLI manual

The current branch also retains the protections already present on
`main` for dry-run isolation, CSS rewrite truncation metadata,
stderr-only progress, shortcut argument forwarding, strict MCP schemas,
and refreshed conflict diagnostics.

## Technical choices

- MCP command orchestration stays in `mcp.ts`; preview port/retry
behavior lives with `mcp-preview`, and persisted checkpoint storage
lives in `mcp-checkpoint`. Each module owns its matching tests.
- Expression validation lives on the shared fragment input schema,
reuses `listFragmentExpressions`, and emits the same structured
validation issues as prop bindings, so JSX-derived and structured
fragments follow one contract across fragment, collection, and runtime
UI insertion.
- Element-child requirements come from component registry content models
during fragment insertion; no component names are hardcoded in the JSX
parser. `CollapsibleTrigger` metadata now matches its enforced `asChild`
implementation.
- Stopped preview status returns only `running: false`; unavailable
`url`, `pid`, and `mode` fields are optional and omitted from JSON.
- Explicit `preview.start` restarts a stale compatible iterative
preview; managed screenshots keep their optimized refresh path.
- Screenshot operations share one timeout utility. A single capture or
complete responsive batch is bounded after preview readiness;
lower-level CDP deadlines use the same stable error code.
- Failed managed captures close and forget their browser session. A
closed browser session cannot restart itself while cancellation is in
progress, which prevents orphaned Chromium processes.
- One ownership helper disposes one-shot and multi-project MCP hosts on
success or failure. Shared `mcp run` hosts dispose in one `finally`
boundary, long-running MCP hosts dispose on connection close, and
Windows uses `taskkill /T /F` instead of killing only the npm wrapper.
- Framework template readers receive the absolute template directory
captured from the build root, so later async work cannot redirect them
to the linked project's cwd. The same option drives cleanup for React
Router, Remix, and Vike.
- Browser startup gets one bounded retry before a rendered capture batch
fails. Persistent failures report only the exit code, signal, or spawn
error code plus the supported `WEBSTUDIO_BROWSER_PATH` recovery action;
local paths and raw browser output are not exposed.
- CLI and MCP preview share one session materializer that ensures the
canonical Builder namespace set before writing `.webstudio/data.json`.
Serialization rejects incomplete snapshots instead of converting missing
style/instance namespaces into valid-looking empty arrays, and preview
freshness records the post-materialization project version.
- The CLI injects its package version and `process.versions.node`; issue
formatting now keeps both fields visible even for legacy reports that
omit runtime metadata.

## Checklist

- [x] Add regression coverage for every changed behavior
- [x] Demonstrate new regression tests fail without the corresponding
fixes and pass with them
- [x] Run affected package test suites and typechecks
- [x] Run lint, package-boundary, and generated API checks
- [x] Review fixture impact — no fixture inputs or outputs are affected
- [x] Review documentation impact and update the authoritative MCP CLI
manual source/generated output
- [x] Run the complete local agent evaluation suite

## Verification

- `@webstudio-is/project-build`: 2,090 tests passed
- MCP asset query result modes are preserved across create, read, and
update serialization
- complete local agent evaluation suite: all 5 fixtures passed,
1,308,276 aggregate tokens
- `@webstudio-is/react-sdk`: 140 tests passed
- `@webstudio-is/template`: 43 tests passed
- `webstudio` CLI: 956 tests passed
- issue reporter: 5 targeted tests passed
- MCP host disposal regression was demonstrated failing without the
shared ownership boundary and passing with it
- relevant package typechecks passed
- `pnpm lint` passed
- `pnpm check:package-boundaries` passed
- `pnpm check:generated-api` passed
- generated-doc tests, typecheck, sync, and regeneration passed; the
aggregate clean-tree command reports the intentionally changed generated
CLI manual
- stalled single capture, stalled four-viewport capture, startup retry
across a four-viewport batch, sanitized browser-exit diagnostics,
complete preview materialization, post-refresh version tracking,
closed-session restart, Windows process-tree cleanup, and
preview-workspace regressions were demonstrated failing without their
fixes and passing with them

## Known limitations

- External `baseUrl` screenshots remain caller-owned and intentionally
do not regenerate, restart, or stop that external server.

Closes #6034
Closes #6035
Closes #6038
Closes #6042
Closes #6043
Closes #6044
Closes #6045
Closes #6046
Closes #6047
Closes #6048
Closes #6049
Closes #6050
Closes #6051
Closes #6052
Closes #6059
Closes #6060
2026-08-08 22:22:25 +02:00
Oleg Isonen 2ec78c183d feat: Add single-result modes to Assets queries (#6040)
## Summary

- Add `many`, `one`, `first`, and `last` result modes to Assets queries
while keeping `many` as the backward-compatible default.
- Return direct items for single-result modes, validate deterministic
ordering, and report duplicate `one` matches as a structured
`MULTIPLE_RESULTS` error.
- Update Builder controls, preview/published runtime handling, SDK
resource encoding, MCP/CLI guidance, generated contracts, and fixtures.

## Implementation TODOs

- [x] Add result-mode schemas and core execution behavior.
- [x] Select single results before content hydration and preserve
deterministic ID tie-breaking.
- [x] Add structured duplicate-match transport errors.
- [x] Update Builder controls and hide/reset pagination for
single-result modes.
- [x] Keep existing settings first, then Output and Result, without a
duplicate Return label.
- [x] Preserve legacy saved resources with an implicit `many` default.
- [x] Update preview, published runtime, SDK, protocol, MCP, and CLI
behavior.
- [x] Regenerate runtime contracts, server metadata, CLI docs, and
fixtures.
- [x] Review documentation impact and update the authoritative MCP/CLI
guidance.
- [x] Add regression coverage and verify the new tests fail without the
implementation.
- [ ] Run the agent evaluation suite (not run; requires explicit user
approval).

## Compatibility and behavior

- Existing queries without `result` continue to behave as `many` and
retain the ID-keyed collection shape.
- `one` returns an item or `null`, but fails when more than one asset
matches.
- `first` and `last` require explicit sorting; the existing asset-ID
tie-breaker makes equal sort values deterministic.
- Single-result modes ignore and do not persist pagination controls.
Materialized collection queries remain collection-only; single-result
queries execute dynamically.

## Verification

- Content engine typecheck and full test suite (291 tests).
- Query builder, query-builder React, SDK, protocol, asset uploader,
project build, Builder, and CLI targeted typechecks/tests.
- Root lint.
- MCP documentation tests, typecheck, sync check, and generated-doc
checks.
- Generated API contract checks.
- Fixture generation pipeline.
- Final focused content-engine regression run (53 tests).
- Final DRY review: affected typechecks, focused tests, lint, package
boundaries, generated checks, and fixtures.

Closes #6039
2026-08-07 19:13:19 +02:00
Oleg Isonen b6d4d645f5 fix: Include runtime metadata in CLI issue reports (#6026)
## Summary

Automatically attach anonymous CLI runtime metadata when the resolved
public API command is `report-issue`. The transport previously compared
the route operation ID (`reports.issue`) with the command name
(`report-issue`), so enrichment was skipped.

The change keeps enrichment owned by the CLI transport and preserves the
existing server contract for Builder UI and other clients.

## Technical choices

- Resolve the public operation by route ID as before.
- Pass its typed command to runtime enrichment, removing ambiguity
between route IDs and commands.
- Exercise the real `reports.issue` transport path and inspect the
serialized request body.
- Keep runtime fields limited to CLI version, Node.js version, OS
family/version, architecture, execution mode, and API contract version.

## Todo

- [x] Attach runtime metadata using the resolved report command.
- [x] Add transport-level regression coverage for the outgoing request.
- [x] Confirm non-report operations preserve existing behavior.
- [x] Review documentation impact; no documentation changes are required
because the public report schema and workflow are unchanged.
- [x] Confirm fixture inputs and generated fixture outputs are
unaffected.
- [ ] Run `pnpm evaluations` after explicit approval for agent
evaluations.

## Verification

- Regression test demonstrated failure before the implementation and
passed afterward.
- `pnpm --filter webstudio test -- project-session.test.ts` — 20 passed.
- `pnpm --filter webstudio test` — 943 passed.
- `pnpm --filter webstudio typecheck` — passed.
- `pnpm exec oxlint --deny-warnings packages/cli/src/project-session.ts
packages/cli/src/project-session.test.ts` — passed.
- `git diff --check` — passed.

## Known limitations

The complete agent evaluation suite has not been run because repository
policy requires separate explicit approval before invoking it.

Closes #6025
2026-08-07 14:21:01 +02:00
Oleg Isonen 6d2389ee54 fix: Stop managed preview process groups (#6037)
## Summary

- start managed POSIX preview servers in their own process group
- terminate the complete preview process group from `preview.stop`
- clean up the owned preview group before propagating `SIGHUP`,
`SIGINT`, or `SIGTERM`
- preserve the existing child-process fallback on Windows
- keep standalone `webstudio preview` signal behavior unchanged

## Root cause

The managed preview controller spawned an npm wrapper and stopped only
that direct child with `ChildProcess.kill()`. React Router runs below
that wrapper, so the dev server could survive as an orphan. Its
inherited stdout/stderr pipes could also keep `webstudio mcp run` alive
after the manifest had completed.

## Impact

`preview.stop` now terminates the npm wrapper and all descendants. If
the CLI is terminated while it owns a managed preview, it kills that
process group before re-propagating the original signal to itself.

## Todo

- [x] Reproduce the missing process-group cleanup in regression tests
- [x] Isolate managed POSIX previews in their own process group
- [x] Stop the full group on `preview.stop`
- [x] Stop the full group on CLI termination signals
- [x] Preserve standalone preview behavior
- [x] Run an independent real subprocess test with an npm wrapper and
long-lived grandchild
- [x] Review documentation impact — existing MCP documentation already
describes `preview.stop` as ending the owned preview, so no docs change
is required
- [x] Verify fixture impact — no fixture inputs or generated outputs are
affected

## Verification

- `pnpm --filter webstudio test` — 58 files, 945 tests passed
- `pnpm --filter webstudio typecheck` — passed
- targeted `oxlint`, `oxfmt`, and `git diff --check` — passed
- regression tests were demonstrated failing without the process-group
and signal cleanup, then passing with it
- independent real-process test on macOS / Node 22.22.1:
- `preview.stop` removed the npm wrapper, server, and long-lived
grandchild in 458 ms; harness exited in 0.93 s
- parent `SIGTERM` removed the complete process group in 0.01 s; harness
exited with expected status 143
  - all observed PIDs were absent afterward
- agent evaluations were not run because they require separate explicit
approval

Closes #5958
2026-08-07 14:19:41 +02:00
Oleg Isonen b8cbd09020 fix: Preserve MCP run results on termination (#6033)
## Summary

- emit a structured terminal result when an active `mcp run` reaches
Node `beforeExit`
- preserve completed call results and identify the unfinished call
- add regression coverage for a four-call run that terminates during
`preview-asset-query`

## Root cause

`mcp run` only wrote its final JSON after the awaited tool call settled.
An unresolved tool promise does not keep Node's event loop alive by
itself, so a read-only preview call could reach `beforeExit` after its
start lifecycle event without producing either a terminal call event or
the run result.

## Implementation

The CLI tracks only the currently active call while a run is executing
and installs one scoped `beforeExit` handler. If Node would exit while
that call is unresolved, the handler reuses the existing MCP run error
envelope to return `MCP_RUN_TERMINATED` with completed results, total
call count, the unfinished call number and tool, elapsed time, and the
original exit code. Existing success, tool-failure, and checkpoint
control flow remains unchanged.

OS-level termination that cannot be intercepted, such as `SIGKILL`,
remains outside the process's ability to report.

## Checklist

- [x] Preserve completed MCP call results on unexpected termination
- [x] Identify the unfinished call in the terminal payload
- [x] Emit a nonzero exit status
- [x] Add fail-then-pass regression coverage for asset query preview
termination
- [x] Review implementation for correctness, unnecessary work, and
duplication
- [x] Review documentation impact: no update needed because this
restores the existing documented stdout JSON contract
- [x] Verify fixture impact: no fixture inputs or generated fixture
outputs are affected

## Verification

- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings packages/cli/src/commands/mcp.ts
packages/cli/src/commands/mcp.test.ts`
- `pnpm --filter webstudio test` — 943 tests passed
- `git diff --check`

The separate `pnpm evaluations` agent evaluation suite was not run
because it requires explicit approval.

Closes #6032
2026-08-07 12:29:20 +02:00
Oleg Isonen 928867a08b fix: Improve MCP CSS variable safety and state feedback (#6018)
Closes #6017
Ref #6019

## Summary

Improve agent-facing CLI/MCP feedback for CSS variables, mutation
durability, batch operations, and preview freshness.

## Implementation

- Validate structured hex colors as normalized channels and recommend
CSS strings for ordinary 0–255 hex input.
- Return exact CSS variable values while retaining `valueLength` in
compact output for compatibility.
- Preserve optional boolean inputs generically in compact MCP SDK
schemas, including `withUsage`, `verbose`, `overwrite`, and `force`.
- Add multi-variable define/delete examples and document safe batching.
- Derive `meta.session.commitStatus` from the session envelope so reads,
plans, failed/no-op mutations, and durable mutations are unambiguous.
- Retain `committed` on read results for compatibility while reporting
`commitStatus: "not-applicable"`.
- Report preview `stale` state and `renderedProjectVersion`, and avoid
marking a preview fresh when the project changes during regeneration.
- Track WSL2/9P cleanup hardening separately in #6019.

## Todo

- [x] Reject out-of-range structured hex color channels and alpha.
- [x] Add fail-before/pass-after CSS color regression coverage.
- [x] Return exact compact CSS variable values without removing
`valueLength`.
- [x] Clarify mutation durability with `commitStatus`.
- [x] Distinguish failed, unchanged, planned, committed, and read-only
results.
- [x] Distinguish server-backed reads from committed mutations at the
session boundary.
- [x] Preserve optional boolean schemas without a per-field allowlist.
- [x] Add multi-variable define and delete examples.
- [x] Report preview freshness and rendered project version.
- [x] Investigate WSL cleanup behavior and create follow-up #6019.
- [x] Review and update authoritative CLI/MCP documentation.
- [x] Verify fixture impact; no fixture inputs or outputs changed.
- [x] Run affected tests, typechecks, lint, and generated-file checks.
- [ ] Run the complete local agent evaluation suite after explicit
approval.

## Verification

- [x] Regression tests demonstrated failing before their implementation
and passing afterward.
- [x] `pnpm --filter @webstudio-is/project-build test` — 82 files, 2,085
tests passed.
- [x] `pnpm --filter webstudio test` — 57 files, 939 tests passed.
- [x] Project-build and CLI typechecks passed.
- [x] `pnpm lint` passed.
- [x] `pnpm oxfmt --check` passed.
- [x] `pnpm check:generated-api` passed.
- [x] `pnpm check:generated-docs` passed.
- [ ] `pnpm evaluations` — awaiting explicit approval per repository
policy.

## Compatibility and limitations

- `committed` remains available on every session result. For reads it is
`false` and durability does not apply; use `commitStatus:
"not-applicable"`.
- Compact CSS-variable records include both exact `value` and
`valueLength`.
- Structured hex color channels remain normalized `0..1`; CSS color
strings such as `"#2d3748"` are the recommended 0–255 representation.
- WSL2/9P process cleanup is not changed in this PR and is scoped in
#6019.
2026-08-06 02:15:51 +02:00
Oleg Isonen 8e7a8a21aa feat: Report anonymous MCP reliability issues (#6021)
Closes #6020

## Summary

Adds the first end-to-end slice for anonymous, LLM-authored reliability
reports from the CLI and MCP.

The shared report contract captures the generalized user story,
attempted workflow, expectation, actual result, recovery attempts, user
impact, technical context, acceptance criteria, and agent runtime
(client, provider, model, version, and reasoning effort). Guidance
requires removing any user- or project-identifying information before
submission.

## Architecture

- Adds a project-authenticated `api.reports.issue` tRPC mutation as the
reusable product boundary.
- Maps that mutation to the public `report-issue` operation for CLI and
MCP callers.
- Keeps the endpoint directly callable from a future Builder UI through
the existing tRPC client.
- Publishes through a server-side GitHub App installation, so end users
do not need GitHub accounts or tokens.
- Discovers the App installation for the fixed Webstudio reporting
repository instead of requiring deployment-specific installation or
repository configuration.
- Searches for a hidden anonymous technical marker before creation to
avoid obvious duplicates.

## Todo

1. [x] Define a shared anonymous LLM-authored report contract.
2. [x] Include the user story, attempted workflow, expected and actual
behavior, recovery attempts, impact, technical context, and acceptance
criteria.
3. [x] Include the LLM client, provider, model, client version when
known, and reasoning effort.
4. [x] Attach sanitized CLI, Node.js, operating-system, architecture,
execution-mode, and API-contract metadata automatically.
5. [x] Add the Builder-reusable project tRPC mutation.
6. [x] Expose the mutation to CLI and MCP as `report-issue`.
7. [x] Add GitHub App authentication and issue publication.
8. [x] Reduce deployment configuration to the App ID and private key.
9. [x] Add marker-based duplicate detection.
10. [x] Add guidance for explicit user-requested reports and qualifying
repeated-friction reports.
11. [x] Review and simplify repeated GitHub request handling and public
protocol exports.
12. [ ] Add server-side throttling and harden concurrent duplicate
creation.
13. [x] Configure and install the reporting GitHub App in deployed
environments.
14. [x] Exercise the deployed endpoint with a controlled test report.
15. [ ] Run the complete agent evaluation suite after explicit approval.
16. [x] Review documentation impact and update the generated MCP
documentation.

## Verification

- Protocol issue-report contract tests: passed (3)
- Builder GitHub reporting tests: passed (4)
- Builder API router tests: passed (30)
- CLI schema, command-doc, MCP, and project-session tests: passed
- Protocol, HTTP client, project-build, CLI, and Builder typechecks:
passed
- Root lint: passed
- Generated API and documentation checks: passed
- New and changed regression tests were demonstrated failing against the
previous behavior, then passing after implementation
- No fixture inputs or outputs are affected
- Agent evaluations were not run because they require explicit approval

## Deployment notes

The Builder only needs `GITHUB_ISSUE_REPORT_APP_ID` and
`GITHUB_ISSUE_REPORT_PRIVATE_KEY`. The server discovers the App
installation for `webstudio-is/webstudio` and creates a short-lived
installation token. The App needs repository metadata read access and
Issues write access.

## Known limitations

Duplicate detection currently uses GitHub issue search and can race when
identical reports arrive concurrently. The endpoint is
project-authenticated, but dedicated server-side throttling remains a
follow-up before production rollout. No Builder UI is included yet; the
tRPC boundary is intentionally ready for one.
2026-08-06 00:32:51 +02:00
Oleg Isonen 57d9ad14cd fix: Retry published CLI registry lookup (#6013)
## What changed

- Retry npm version and integrity resolution inside the CLI registry
smoke test.
- Keep retry timing injectable so the propagation race is covered by a
fast unit test.
- Preserve the final npm error when all registry attempts are exhausted.

## Why

CLI 0.286.0 was published successfully, but npm briefly exposed
integrity metadata while the exact version lookup still returned 404.
The post-publish smoke failed and the dependent docs publication job was
skipped.

The 0.286.0 documentation snapshot has been recovered directly on the
`docs` branch.

Ref #6007

## Verification

- [x] Regression test demonstrated failing before the implementation and
passing afterward
- [x] CLI registry/versioning tests: 3 passed
- [x] CLI typecheck
- [x] Formatting and diff checks
- [x] Documentation impact reviewed: no product-doc source change is
needed in this PR; the missed 0.286.0 snapshot was published separately
to `docs`
2026-08-05 22:03:26 +02:00
Oleg Isonen 6385e290c6 feat: Add syntax highlighting to Code Text (#6007)
Closes #4119

Supersedes #5999 after GitHub closed it during the head-branch rename.

## Approach

Code Text uses one optional public highlighting boundary:

- `@webstudio-is/sdk-components-react/code-text` → `src/code-text.tsx`

The ordinary `components` entry remains Shiki-free. Canvas loading,
metadata options, theme variables, and the plain semantic `<code>`
component remain private implementation details.

**Code** uses the code editor. **Language** and **Theme** use normal
bindable selects. Fixed selections generate direct imports for only the
per-page language/theme union; plaintext imports no grammar. Bound
selections generate Shiki's lazy language/theme registries.

SSR and client rendering share the same factory. Missing bound assets
suspend the first render while SSR resolves the selected modules. SSG
now waits for Suspense before serializing HTML. During hydration the
completed server markup stays in place while the browser fetches only
the selected language and theme. Later client-side binding changes load
only the new selections and keep the same `<code>` node and focus.

Highlighted output preserves the semantic root, ref, author
classes/styles, whitespace, escaped source, and legacy children/native
`lang` behavior. Shiki's implicit tab stop is disabled. A color-only
transformer strips theme typography so asynchronous highlighting cannot
change text geometry.

## Bundle impact

Measured from same-commit production React Router + Cloudflare builds
containing one JavaScript / GitHub Light Code Text instance. Sizes are
the sum of emitted JavaScript files; gzip is the sum of each file
compressed independently.

| Worker output | Files | Raw | Sum gzip |
| --- | ---: | ---: | ---: |
| Fixed selections | 3 | 1,341,851 B | 288,342 B |
| Bound selections | 310 | 10,372,112 B | 1,872,273 B |
| Increase | +307 | +9,030,261 B (8.61 MiB) | +1,583,931 B (1.51 MiB) |

The bound browser build also emits the catalog as lazy chunks:
+9,012,594 raw bytes / +1,578,779 sum-gzip bytes across the complete
deployed output. Those chunks are not all delivered on page load. The
production SSG regression verifies 100+ catalog chunks are emitted while
the HTML directly module-preloads only one shared runtime chunk; the
browser fetches the runtime-selected language/theme modules on demand.

## Tradeoffs and compatibility

- Instances with both highlighting properties unset keep the legacy
plain renderer.
- Static highlighting still requires both **Language** and **Theme**.
- Binding either selection includes that catalog in deploy output
because its runtime value is not known at publish time.
- The syntax-highlighting property is `language`; legacy native HTML
`lang` remains preserved.
- Invalid or unavailable runtime selections render as plain code.
- Fixed selections retain the smallest publish output; bound selections
trade deploy/worker size for runtime flexibility.

## Todo

- [x] Restore the specialized Code Text component and semantic plain
fallback
- [x] Use a code editor for Code and bindable selects for Language and
Theme
- [x] Persist JavaScript / GitHub Light template defaults
- [x] Collapse highlighting to one public `./code-text` entry
- [x] Keep canvas loaders and option/theme helpers private
- [x] Generate direct imports for fixed selections and omit plaintext
grammar
- [x] Generate lazy catalogs when Language or Theme is bound
- [x] Preserve highlighted SSR markup during hydration without
node/focus movement
- [x] Support Suspense-aware highlighted SSG output
- [x] Preserve escaping, accessibility, legacy children, and native
`lang`
- [x] Measure fixed versus bound Cloudflare worker output
- [x] Deep-review correctness, DRYness, package surface, and obsolete
binding infrastructure
- [x] Review documentation impact and update Code Text documentation
- [x] Run focused tests, typechecks, production builds, formatting, and
staged diff review
- [ ] Fixture pipeline — blocked during sync because the current API
rejects this local CLI version as incompatible; no fixture output
changed

## Verification

- [x] New/changed regressions were demonstrated failing without their
behavior and passing after restoration
- [x] SDK Code Text metadata, rendering, hydration, and bundle tests: 17
passed
- [x] Component registry tests: 3 passed
- [x] CLI asset collection and full prebuild suite: 59 passed
- [x] SDK components, component registry, SDK, and CLI typechecks
- [x] SDK components declaration and production builds
- [x] Component registry production build
- [x] Production Vite + Vike prerender emits highlighted bound SSG HTML
with one doctype
- [x] Dynamic catalog chunks remain outside the SSG page's direct
preload set
- [x] Documentation reviewed and updated
- [x] Formatting and staged diff checks
2026-08-04 18:25:36 +02:00
Oleg Isonen 1ca01e96a3 docs: Guide MCP agents to build dynamic JSON-LD (#5998)
## Summary

Clarify how MCP agents should author JSON-LD without serializing dynamic
values prematurely. Static structured data remains a validated compact
JSON string, while runtime-backed structured data uses an expression
that evaluates directly to an object or array. The focused guide now
provides machine-readable input recipes for both paths.

## Implementation

- [x] Update the focused `components.get` JSON-LD guidance.
- [x] Update the JSON-LD `meta.guide` workflow and include `bind-props`.
- [x] Add structured `fixedCodeUpdate` and `dynamicCodeBinding` recipes.
- [x] Document that expression source is stored as text while its
evaluated value must be an object or array.
- [x] Warn agents not to call `JSON.stringify` or assemble JSON with
string concatenation.
- [x] Update CLI use-case documentation and generated docs.
- [x] Parse the fixed JSON and dynamic expression AST in regression
coverage, including the `post.title` runtime binding.
- [x] Demonstrate that the semantic regression test fails when the
structured recipe is absent.

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,066 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter webstudio typecheck`
- Focused MCP semantic regression tests
- `pnpm oxfmt --check ...`
- `pnpm oxlint packages/project-build/src/mcp.ts
packages/project-build/src/mcp.test.ts`
- `pnpm check:generated-docs`
- Fixtures not run: documentation and MCP guidance do not affect fixture
inputs or generated fixture bundles.
- Agent evaluations not run: explicit approval was not provided.

Closes #5997
2026-08-03 23:36:03 +02:00
Oleg Isonen 1f4095cf9a fix: Improve generated MCP documentation (#5991)
## Summary

- render the generated MCP version as visible body content immediately
below the page heading
- keep the GitBook navigation label stable as `MCP`
- stamp only the CLI version placeholder during release publication,
leaving Markdown content owned by the template
- remove empty `{}` blocks from the rendered MCP argument examples while
preserving meaningful examples and complete structured JSON data

## Root cause

GitBook uses the `SUMMARY.md` link label as the published page heading
and suppresses the Markdown H1 from the document body. The generated
version was therefore present in source but absent from the rendered
page.

The release workflow also coupled version stamping to the complete
rendered Markdown line. It now replaces and verifies only the standard
`0.0.0-webstudio-version` placeholder.

## Verification

- live page structure inspected: published H1 is `MCP`
- MCP manual regression test demonstrated failing before empty argument
blocks were filtered, then passing after the fix
- release placeholder stamping simulated against the generated page
- `pnpm --filter webstudio exec vitest run src/commands/man.test.ts`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter webstudio typecheck`
- `pnpm check:generated-docs`
- `pnpm lint`
- `pnpm oxfmt --check packages/project-build/src/mcp.ts
packages/cli/src/commands/man.ts packages/cli/src/commands/man.test.ts`
- `git diff --check`

Fixture inputs and outputs are unaffected.

## Todo

- [x] Generate the release version as visible GitBook body content.
- [x] Decouple release stamping from the rendered Markdown content.
- [x] Remove empty MCP argument example blocks.
- [x] Validate generated output and regression coverage.
- [ ] Merge this pull request.
- [ ] Publish the latest released revision to the `docs` branch.
- [ ] Verify the version on the live GitBook page.

Ref #5986
2026-08-03 20:21:35 +02:00
Oleg Isonen e855a1e7cf fix: Skip unenumerated dynamic SSG pages (#5982)
## Outcome

Restore SSG downloads for projects that contain dynamic pages without
enumerable Assets query paths. Static pages continue to export, while
unsupported dynamic routes are omitted as they were before Assets-backed
SSG prerendering was introduced.

Closes #5981

## Root cause

Assets-backed dynamic prerendering changed the SSG framework from
skipping every dynamic route to throwing whenever the computed prerender
path list was empty. That empty list also represents existing dynamic
routes without a configured Assets query, so otherwise valid static
downloads began failing during scaffolding.

## Implementation

- Return no framework files for dynamic pages whose prerender path list
is empty.
- Preserve generated Vike routes for Assets-backed pages that enumerate
at least one path.
- Preserve the existing prebuild validation errors for Assets queries
that bind parameters incompletely or cannot enumerate every query
branch.
- Add an integration regression test covering a static home page
alongside `/blog/:slug` with no Assets query.

## Todo

- [x] Reproduce the production failure in a regression test
- [x] Restore omission of unenumerated dynamic routes
- [x] Verify Assets-backed dynamic SSG behavior remains passing
- [x] Run focused lint, formatting, typecheck, and integration tests

## Verification

- [x] New regression test demonstrated failing before the implementation
change and passing afterward
- [x] `pnpm --filter webstudio test -- src/prebuild.test.ts` — 49 passed
- [x] `pnpm --filter webstudio typecheck`
- [x] `pnpm exec oxfmt --check packages/cli/src/framework-vike-ssg.ts
packages/cli/src/prebuild.test.ts`
- [x] `pnpm exec oxlint --deny-warnings
packages/cli/src/framework-vike-ssg.ts
packages/cli/src/prebuild.test.ts`
- [x] `git diff --check`

## Notes

- No fixture inputs or generated fixture outputs are affected.
- The agent evaluation suite was not run because repository policy
requires separate explicit approval.
- Production failure:
https://github.com/webstudio-is/webstudio-saas/actions/runs/30769678212/job/91554478283
2026-08-03 00:55:30 +02:00
Oleg Isonen 92084aeea3 fix: Remove MCP workflow friction (#5974)
## Summary

- Make MCP discoverable from the CLI README and expose direct MCP tool
calls as restart-free CLI subcommands.
- Preserve required discriminator fields in compact MCP SDK schemas and
surface the `set-text-content` contract.
- Return instance props by default from `inspect-instance` and improve
the `list-instances` input hint.
- Add a Builder context-menu **Move** submenu as an accessible
alternative to drag-and-drop sibling reordering.

Companion generated documentation:
https://github.com/webstudio-is/webstudio-community/pull/274

## Technical choices and compatibility

- Compact SDK schemas retain required fields only inside root
composition branches, avoiding broad schema expansion.
- `inspect-instance` now defaults to `props` only; larger styles,
bindings, sources, children, and ancestors remain opt-in.
- Existing move commands and shortcuts back the new menu, so command
behavior stays centralized.
- No migrations, fixture inputs, generated fixture bundles, or generated
API contracts are affected.

## Todo

- [x] Add direct CLI MCP discovery and restart guidance.
- [x] Preserve discriminated-union requirements for SDK clients.
- [x] Improve instance inspection and input diagnostics.
- [x] Add accessible menu-based instance movement.
- [x] Add regression coverage and verify fail/pass behavior.
- [x] Run targeted tests, typechecks, lint, package-boundary, and
generated-doc checks.
- [ ] Deferred: run `pnpm evaluations` after separate explicit approval,
as required by repository policy.

## Verification

- `@webstudio-is/project-build`: 172 targeted tests passed.
- `@webstudio-is/protocol`: 8 targeted tests passed; typecheck passed.
- `@webstudio-is/builder`: 8 targeted tests passed; typecheck passed.
- CLI and project-build typechecks passed.
- Root lint, package-boundary check, generated-doc check, and diff
checks passed.
- New and modified regression tests were demonstrated failing before
their fixes and passing afterward.

## Known limitations

Synthetic drag-and-drop behavior is unchanged. The new menu and existing
keyboard shortcuts provide the automatable and accessible reordering
path. Agent evaluations were not run because explicit approval was not
provided.
2026-08-03 00:36:56 +02:00
Oleg Isonen f335340a54 fix: Correct content database sizing and Markdown references (#5972)
## Summary

- Report only serialized content-database bytes that are actually
included in the published bundle.
- Replace the ambiguous `markdown-body` query mode with the strict
`markdown-body-ref` mode.
- Require every selected Markdown body to have a document-graph node;
compilation now fails instead of embedding source bytes as a fallback.
- Apply the same Markdown eligibility rule during compilation and
runtime query execution, excluding non-Markdown files from
reference-only databases and results.
- Highlight database sizes in red only when documents were omitted
because of the size limit.
- Refresh Assets diagnostics after an Assets resource edit is confirmed
by the server.
- Update CLI/MCP guidance, examples, schemas, generated contracts, and
fixtures to use the reference-only mode.

## Root cause

Dynamic Markdown-body queries conservatively treated unrelated files,
including remote images, as possible hydration candidates. The old mode
could also fall back to embedding a Markdown body when graph resolution
was unavailable, despite its intended storage-backed behavior.
Compilation and runtime selection did not previously share the same
content-mode eligibility check. Diagnostics displayed the unbounded
pre-limit estimate instead of the serialized bundled size, did not carry
the omission reason into query-preview diagnostics, and an early cached
preview could leave the merged measurement stale until reload.

## Impact

Content database diagnostics and publish warnings now reflect bundled
database bytes rather than remote source-file sizes. Saved query filters
trigger a post-sync diagnostics refresh. Markdown detail queries must
use `content.mode: "markdown-body-ref"`; the old `markdown-body` value
is rejected, missing graph support is an explicit compilation error, and
non-Markdown assets cannot be selected or counted by a reference-only
query.

Ref #5966

## Verification

- Content engine: 39 files / 261 tests passed.
- Asset uploader: 22 files / 183 tests passed.
- SDK: 29 files / 505 tests passed.
- Protocol: 9 files / 86 tests passed.
- Project build: 82 files / 2,055 tests passed.
- CLI: full suite previously passed (57 files / 933 tests); final
prebuild verification passed 48 tests.
- Focused Builder diagnostics tests: 4 files / 10 tests passed.
- Typechecks passed for Builder, content engine, asset uploader, SDK,
protocol, project build, and CLI.
- Generated API contracts and docs are current.
- Fixture build passed and is reproducible after the final review
changes.
- Formatting, lint, package-boundary checks, and diff checks passed.
- New strict-reference, runtime-selection, compilation-selection, and
size-highlight regression tests were demonstrated failing without their
implementations and passing with them.
- Agent evaluations were not run because this follow-up did not receive
separate evaluation approval.
2026-08-02 12:20:10 +02:00
Oleg Isonen 56a22fde8a feat: Add storage-backed document graph resolution (#5966)
## Summary

Introduce a document graph architecture for Assets resources so
structured documents can reference one another while keeping full
document payloads on CDN-backed storage.

The content database will retain the searchable index and dependency
graph. At runtime, the server will select a root document, determine its
complete dependency set before performing I/O, fetch each unique
document concurrently, and assemble the final result server-side.

## Implementation todos

### Document graph foundation

- [x] Add normalized internal reference contracts for whole documents,
JSON paths, Markdown body, and Markdown frontmatter.
- [x] Add an immutable graph abstraction with deterministic ordering,
dependency traversal, revision validation, and cycle detection.
- [x] Add a storage-independent resolver with dependency closure,
bounded parallel loading, shared-content deduplication, representation
selection, assembly hooks, cancellation, and structured errors.
- [x] Add focused unit coverage for the isolated document-graph
abstraction.
- [x] Define and validate the external remote-document reference syntax.
- [x] Add a pure graph compiler that resolves canonical document URLs
into IDs and revisions and validates missing, duplicate, and cyclic
dependencies.

### Build-time graph

- [x] Add a storage-independent source-catalog compiler for bounded
parallel reference discovery and deterministic graph compilation.

- [x] Add a versioned deterministic document graph artifact with
canonical serialization and integrity verification.

- [x] Discover and normalize statically declared references while Assets
resources are processed.
- [x] Resolve document identities and revisions and report missing,
stale, or cyclic dependencies with document context.
- [x] Persist document nodes and dependency edges alongside searchable
resource metadata.
- [x] Include persisted graph data in artifact integrity and size
validation.

### Document representations

- [x] Add a standalone JSON document adapter for exact reference
markers, compiler occurrences, JSON path selection, and immutable
assembly.
- [x] Add a standalone Markdown document adapter for bounded decoding,
frontmatter references, immutable assembly, and source, body, and
frontmatter selection.

- [x] Parse fetched JSON and Markdown documents through format-specific
adapters.
- [x] Select complete documents, JSON fragments, Markdown body, and
Markdown frontmatter through the shared reference model.
- [x] Assemble nested and shared references into the final server-side
value.

### Runtime resolution

- [x] Enforce reachable-document, per-document byte, and aggregate-byte
limits before parsing and assembly.

- [x] Add a revision-keyed bounded source-cache wrapper with replayable
bytes, stale-entry fallback, and in-flight deduplication.

- [x] Add a storage-neutral loaded-source contract that rejects revision
mismatches before parsing.

- [x] Resolve graph-backed property paths before filtering, sorting,
pagination, and projection; skip graph loading for unrelated queries.
- [x] Add an injected HTTP/CDN document loader with revision metadata
validation.
- [x] Apply runtime payload limits, bounded concurrency, caching, and
structured failure mapping.
- [x] Ensure shared documents are fetched and parsed once per
resolution.

### Product integration

- [x] Integrate equivalent behavior in local preview, SSR, SSG, and
published runtime paths.
- [x] Preserve existing embedded Assets resource behavior without
requiring migration.
- [x] Add observability for graph loading, document fetching, caching,
and resolution failures.
- [x] Document the public reference and resolution behavior.

### Review hardening

- [x] Enforce graph, artifact, cache, and published-storage identity
consistency.
- [x] Compile only graph sources reachable from selected build entries.
- [x] Resolve graph-backed fields before query evaluation and avoid
unnecessary graph loading.
- [x] Consolidate duplicated graph schemas, utilities, and document
content-type detection.
- [x] Correct runtime documentation and use structural evaluation
validation.

### Verification

- [x] Keep a deterministic high-impact agent evaluation for
Markdown-to-JSON references across the dynamic blog overview and article
routes.
- [x] Add a separate discoverability evaluation that must learn the
reference workflow from MCP guidance without prompt-supplied syntax,
queries, bindings, or call order.
- [ ] Re-run the complete local agent evaluation suite after the
discoverability fixture change and compare every fixture with its
accepted baseline.

- [x] Add integration coverage for CDN-backed hydration, parallel
fetching, caching, and shared dependencies.
- [x] Add integration coverage for missing references, cycles, revision
changes, limits, and cancellation.
- [x] Verify equivalent behavior across preview, SSR, SSG, and published
deployments.
- [x] Verify that existing embedded Assets resources retain their
current behavior.

## Intended outcome

- Storage-backed documents do not require their complete payloads to be
embedded in the content database.
- The server knows the complete statically discoverable dependency set
before fetching document content.
- Shared dependencies are fetched once per resolution and independent
resources can be fetched in parallel.
- JSON fragments and Markdown body/frontmatter representations use the
same document-resolution system.
- Reference and payload revisions cannot be mixed silently.
- Existing Assets resources continue to work without migration.

## Verification results

- `@webstudio-is/content-engine`: 37 files / 244 tests passed; typecheck
passed.
- `@webstudio-is/asset-uploader`: 22 files / 182 tests passed; typecheck
passed.
- CLI: 57 files / 925 tests passed, including generated SSR and SSG
build/prerender coverage; typecheck passed.
- Repository-wide `pnpm oxfmt --check` passed across 2,757 files.
- Package-boundary validation passed.
- Previous `pnpm evaluations` run: all 4 original high-impact fixtures
passed. The complete 5-fixture rerun is pending explicit approval after
adding the discoverability fixture.
2026-08-02 10:39:06 +02:00
Oleg Isonen 3ccb46cec7 fix: restore Marketplace settings and optimize Assets publishing (#5964)
## Outcome

Restore the Marketplace section for projects without existing
marketplace metadata, make the complete review-submission workflow
available over MCP, clarify shared Assets database diagnostics, and
reduce published database size by materializing fully static Assets
queries.

## Implementation

### Marketplace and MCP

- Initialize the Marketplace form from a safe partial default instead of
rendering nothing when metadata is unconfigured.
- Keep review submission disabled until the complete metadata schema is
valid.
- Allow `get-marketplace-product` to return `null` and
`update-marketplace-product` to create the first marketplace product
through ProjectSession/MCP.
- Add a confirmed `submit-marketplace-product` server operation that
validates configured metadata before setting review status to `PENDING`.
- Document the MCP workflow: upload the thumbnail, update metadata,
publish production, then submit.
- Correct the Marketplace thumbnail upload example to use the current
MCP input field.

### Assets database

- Show query size and merged published-database size separately in
resource diagnostics.
- Clarify that only the merged database counts toward the publish limit.
- Allow the Assets query editor to scroll long query source.
- Materialize fully static metadata-only Assets queries at publish time.
- Store materialized results as compact field columns and rows keyed by
a deterministic normalized-query hash.
- Serve materialized rows through the existing Assets response contract
without candidate documents or a runtime field catalog.
- Keep route- or variable-dependent queries on the existing
document-backed runtime path.
- Exclude overview-only fields from dynamic detail documents so the
merged database does not duplicate them.
- Preserve byte-limit selection, truncation diagnostics, integrity
verification, caching, and backward compatibility with existing
artifacts.

### Release

- Keep the release lockfile stable when preparing the CLI package.
- Regenerate runtime contracts, server-only API metadata, and CLI
documentation where required.

## Todo

- [x] Restore the empty Marketplace settings state
- [x] Prevent empty review submissions
- [x] Support first-time Marketplace metadata creation over MCP
- [x] Add MCP review submission
- [x] Document the end-to-end MCP workflow
- [x] Correct query and merged-database diagnostics
- [x] Make long Assets query source scrollable
- [x] Materialize static overview queries
- [x] Preserve dynamic detail-query behavior
- [x] Add regression coverage with fail/pass proof
- [x] Run relevant package and integration checks

## Verification

- [x] Content engine tests: 158 passed
- [x] Asset uploader tests: 179 passed
- [x] SDK tests: 505 passed
- [x] CLI tests: 921 passed
- [x] CLI prebuild integration tests: 46 passed
- [x] Builder Assets resource and diagnostics tests: 14 passed
- [x] Project runtime and ProjectSession tests: 133 passed
- [x] Builder API router tests: 30 passed
- [x] Protocol operation tests: 18 passed
- [x] Content engine build and declaration generation
- [x] Content engine, asset uploader, SDK, Builder, and CLI typechecks
- [x] Repository lint and formatting checks
- [x] Generated API and documentation checks
- [x] New materialization tests demonstrated failing with the
implementation disabled and passing after restoration

## Notes

- The representative five-post overview fixture shrinks from 2,122 bytes
to 1,197 bytes while returning the same sorted and paginated result.
- Materialization is intentionally limited to static, metadata-only
field queries without runtime asset fields. Unsupported or
data-incompatible queries safely use the existing runtime database.
- The complete `pnpm evaluations` agent suite was not run because
repository policy requires separate explicit approval.
- No database schema or migration changes are required.
2026-07-31 13:16:21 +02:00
Oleg Isonen 3aeb4b3f61 fix: Reuse npm launcher for generated previews (#5962)
## Summary

Generated previews now reuse the npm CLI path supplied by the npx
launcher instead of rediscovering npm or npm.cmd through the MCP
subprocess PATH. The same invocation is used for dependency
installation, production builds, and preview server startup, with the
existing platform command retained as a fallback for direct launches.

## Technical choices

- Invoke npm-cli.js with the current Node executable when npm_execpath
identifies npm as the launcher.
- Parse Windows paths with the platform path implementation.
- Keep npm and npm.cmd fallback behavior when Webstudio was not launched
through npm.
- Inject executable paths in tests so launcher behavior is
deterministic.

## Todo

- [x] Reuse the npx npm launcher during generated dependency
installation.
- [x] Apply the same launcher to generated build and server commands.
- [x] Cover Windows launcher paths and direct-launch fallbacks.
- [x] Run focused tests, typecheck, lint, formatting, and production
build.

## Verification

- pnpm --filter webstudio test -- src/commands/preview.test.ts
src/preview-server.test.ts
- pnpm --filter webstudio typecheck
- pnpm exec oxlint --deny-warnings on the four changed files
- pnpm --filter webstudio build
- git diff --check

All checks passed. The build emitted the existing Radix use-client
bundling warnings. Agent evaluations were not run because they require
separate explicit approval.
2026-07-31 00:56:30 +02:00
Oleg Isonen e210fedc02 fix: support Cursor-compatible MCP tool names (#5961)
## Summary

- configure Cursor connections to request underscore-only MCP tool names
- advertise Cursor-safe names while mapping calls back to canonical
Webstudio operations
- preserve canonical tool names for other MCP clients and shell
workflows

## Verification

- pnpm --filter webstudio test -- src/commands/connect.test.ts
src/commands/mcp.test.ts
- pnpm --filter @webstudio-is/project-build test -- src/mcp.test.ts
- pnpm --filter webstudio typecheck
- pnpm --filter @webstudio-is/project-build typecheck
- pnpm exec oxlint --deny-warnings on changed files
- pnpm exec oxfmt on changed files
- pnpm evaluations not run (requires separate explicit approval)

Closes #5960
2026-07-31 00:37:21 +02:00
Oleg Isonen efbc3098c4 feat: add queryable Assets resources (#5946)
Closes #5919
Ref #5920

## Outcome

Extend the existing Assets system resource with optional queries for
Markdown, JSON, and standard file metadata. Unconfigured Assets
resources retain the existing fetch-all behavior and activate none of
the content-query infrastructure.

Files are the authoring model. A standalone content engine converts an
immutable snapshot of only the files required by configured queries into
one bounded, deterministic runtime database. Builder preview, SSR, and
SSG use the same query semantics without requiring a database service
for a published site.

## Final architecture

```text
Markdown / JSON files
        │
        ▼
ContentSource adapter
(FS or Webstudio Assets)
        │ immutable snapshot
        ▼
ContentCompilationPlan
(union of configured query requirements)
        │
        ▼
ContentCompiler
(parse, normalize, discover fields, enforce budget)
        │
        ▼
ContentArtifact
(bounded, deterministic, immutable JSON)
        │
        ▼
ContentDatabase
(filter, OR groups, sort, paginate, project, read content)
        │
        ├── Builder preview
        ├── SSR runtime
        └── SSG build only
```

The authoring/storage layer owns mutable files. The content engine
consumes immutable snapshots and produces immutable, disposable
artifacts.


Query authoring is a separate OpenAPI-driven path:

```text
Assets REST OpenAPI operation
+ standard request JSON Schema
+ dynamic field paths
        │
        ▼
Generic OpenAPI adapter
        │ resolved schemas, controls, paths, and transport mapping
        ▼
Generic query-builder core and React renderer
        │ arbitrary provider-defined value
        ▼
Assets REST request adapter
        │
        ▼
{ query: value }
```

The selected standard OpenAPI operation is the only source of query UI
semantics. Request parameters and JSON Schema describe the editable
data; the generic renderer derives controls and owns layout.

### Activation and compilation

- The compiler does not index every asset. It combines all reachable
configured Assets-resource queries into one `ContentCompilationPlan`.
- Static constraints such as folder, path, and file type select
candidate files. Dynamic values such as a route-bound slug cannot reduce
the build-time candidate set.
- The plan contains the union of fields used by filtering, sorting,
routing, projection, excerpts, and requested full content.
- Documents shared by multiple resources are stored once in one
deployment-wide runtime database.
- Markdown/frontmatter, JSON properties, excerpts, and full bodies are
prepared only when required.
- No configured content query means no parsing, artifact, or shipped
runtime dependency. Fresh generated apps need no content-runtime
cleanup; incremental generation removes stale integration if a
previously configured query is deleted.

### Runtime database limit

- `CONTENT_DATABASE_MAX_BYTES` limits the complete serialized runtime
database, not source files, images, or unrelated assets.
- The default is `500 * 1024` bytes and is presented as 500 KiB.
- Counted data includes indexed metadata, frontmatter/JSON properties,
excerpts, the field catalog, serialization overhead, and full Markdown
bodies only when requested.
- Prepublish validation builds and serializes the exact database that
will be attached to the immutable build, so it knows the unbounded size,
bounded size, included documents, omitted documents, and affected
resources.
- Overflow never stores partial documents. Candidates are ordered by
`createdAt` descending, canonical path ascending, and stable document ID
ascending. Missing dates follow dated documents. A document that does
not fit is skipped while smaller later documents are still considered.
- Exceeding the limit warns but does not block publishing.
- Builder preview can report exact bounded versus unbounded results when
dynamic expressions have concrete preview values. Prepublish warnings
remain conservative for future route values.

### Snapshot consistency

The source contract guarantees a consistent compilation, while adapters
provide the mechanism:

- Webstudio Assets captures asset metadata and immutable content
references.
- Filesystem sources capture file identity and verify files did not
change before accepting the artifact.
- A changed source retries once, then fails clearly instead of producing
a mixed-revision database.
- This requires no universal snapshot service, source-tree copy, or new
versioned-asset infrastructure.

### Package ownership

- `@webstudio-is/content-engine`: runtime artifact schema, query
execution, field catalog, compact runtime statistics.
- `@webstudio-is/content-engine/compiler`: source snapshots, compilation
plans, Markdown/JSON parsing, deterministic compilation, byte
accounting, and compiler diagnostics. This entry exists to keep
Remark/YAML/future MDX dependencies out of runtime bundles.
- `@webstudio-is/query-builder`: resolves standard OpenAPI operation
schemas into an internal provider-neutral editor model, validation,
mutations, and source serialization. It owns no Assets,
provider-authored UI contract, or fixed REST-query semantics.
- `@webstudio-is/query-builder/react`: container-neutral schema renderer
whose layout is renderer policy. It remains separate from the content
engine.
- SDK: Webstudio Assets/Resource domain models and mapping Webstudio
expressions into generic content requirements.
- Protocol: external Assets REST/OpenAPI transport contracts.
- Asset uploader/repository: asset and folder mutations, generic
metadata, immutable byte references, and storage access. Its hosted
adapter supplies Webstudio Assets snapshots and parsed-metadata cache
persistence to the content engine; parsing, artifact creation, and query
semantics remain engine-owned.
- Builder and generated-app integrations decide when to compile, cache,
embed, load, or discard a database.

### Runtime lifecycle

- Builder caches immutable `ContentDatabase` instances by source
revision plus compilation-plan hash and coalesces concurrent
compilation. Matching operations reuse the same instance; field
discovery uses a dedicated minimal all-properties plan that excludes
excerpts and bodies.
- SSR attaches the validated bounded database to the build and includes
the small runtime only when required.
- SSG uses the database only while generating pages and omits it and the
runtime from final output unless a future runtime-query feature requires
them.
- Local development compiles from the filesystem and uses identical
query semantics.
- Published pages query their local artifact; they do not call Builder
REST endpoints.
- Published artifacts need no external index store, invalidation,
garbage collection, SaaS content proxy, new bucket, or new environment
variable.

## Implemented foundation

These existing parts remain useful and should be moved behind the final
ownership boundaries rather than reimplemented.

- [x] Upgrade the existing Assets system resource in place and preserve
unconfigured fetch-all behavior.
- [x] Implement typed filtering, nested boolean groups, deterministic
sorting, pagination, projections, dynamic values, and content hydration.
- [x] Add Markdown frontmatter, excerpt, JSON-property, and
field-catalog extraction.
- [x] Add reusable query-control implementations and a separately
imported React UI as a foundation for the final OpenAPI-driven contract.
- [x] Add Assets query authoring and dynamic field discovery as existing
behavior to migrate into the Assets OpenAPI operation.
- [x] Add synchronized visual and editable expression authoring.
- [x] Unify Assets REST operations under `/rest/assets/*`.
- [x] Define authenticated collection, item, content, upload, folder,
query, field-catalog, and OpenAPI operations.
- [x] Support Builder cookie authentication and public API token
authentication.
- [x] Add Builder preview, MCP/CLI operations, SSR execution, SSG path
generation, and conditional activation.
- [x] Add revision-keyed parsed metadata persistence for the hosted
Builder.
- [x] Add focused query, API, authorization, Builder, CLI, SSR, SSG,
migration, and fixture coverage.

## Remaining implementation plan

### 1. Establish the content engine

- [x] Rename the generic `@webstudio-is/asset-resource` implementation
to `@webstudio-is/content-engine`.
- [x] Move existing behavior instead of creating a parallel
implementation.
- [x] Keep the primary entry runtime-only and add only the compiler
entry needed for bundle separation.
- [x] Remove the old package after all consumers migrate.
- [x] Ensure the engine has no Builder, SDK, asset-uploader, PostgreSQL,
R2, or deployment-target dependency.

### 2. Define source snapshots and compilation plans

- [x] Introduce `ContentSource.openSnapshot()` and a revision-bearing
`ContentSourceSnapshot`.
- [x] Include canonical path, stable document ID, content type,
immutable content reference, size, and optional `createdAt`.
- [x] Implement Webstudio Assets and filesystem snapshot guarantees
using their adapter-specific mechanisms.
- [x] Introduce `ContentCompilationPlan` for the union of reachable
configured resource requirements.
- [x] Separate statically selectable candidates from dynamic runtime
predicates.
- [x] Add mutation-during-compilation tests for replacement, deletion,
folder movement, and filesystem changes.

### 3. Separate storage from content processing

- [x] Keep upload, replacement, folders, byte storage, revisions, and
generic metadata in the Assets repository/uploader.
- [x] Move parsing, excerpts, field discovery, query execution, and
artifact creation into the content engine.
- [x] Remove content-index coordination from asset mutation business
logic.
- [x] Preserve original source `createdAt` across content replacement
and expose it as optional queryable metadata.
- [x] Keep hosted parsed-document persistence behind the content
compiler's optional cache interface.

### 4. Build deterministic compilation

- [x] Move the existing Markdown, frontmatter, JSON, canonicalization,
hashing, document-building, and tests into the compiler.
- [x] Keep frontmatter behind a dedicated module and continue using the
existing Remark/Micromark pipeline.
- [x] Parse only candidate files and only the tiers required by the
compilation plan.
- [x] Deduplicate documents and fields required by multiple resources.
- [x] Define a versioned, deterministic `ContentArtifact` with integrity
information.
- [x] Keep verbose parse and omission diagnostics outside the runtime
artifact.
- [x] Reject incompatible artifact versions cleanly.

### 5. Enforce the deployment-wide database budget

- [x] Replace the old asset-index limit with
`CONTENT_DATABASE_MAX_BYTES`, defaulting to 500 KiB.
- [x] Have applications read configuration and pass the byte limit into
the compiler.
- [x] Measure the exact serialized database, including selected bodies
and overhead.
- [x] Implement deterministic skip-and-continue packing of complete
documents.
- [x] Return compiler diagnostics with unbounded/bounded bytes,
included/omitted counts, omitted identities, largest contributors, and
affected resources.
- [x] Expose only compact artifact statistics at runtime through
`ContentDatabase.getStats()`.
- [x] Add exact boundary, deterministic ordering, deduplication, and
oversized-document tests.

### 6. Finalize the runtime database

- [x] Define `ContentDatabase` over an immutable artifact.
- [x] Preserve filtering, nested AND/OR groups, sorting, offset/limit,
projection, field references, and content retrieval.
- [x] Expose `getFieldCatalog()` and compact `getStats()`.
- [x] Keep Markdown/YAML/compiler dependencies out of the runtime entry.
- [x] Add execution guards and maximum-size runtime benchmarks.

### 7. Make query authoring OpenAPI-driven

The selected standard OpenAPI operation is the complete query-authoring
contract. The query builder resolves parameters and request-body JSON
Schema, derives its internal editor model, and applies renderer-owned
layout policy. It must not embed or fetch a second capabilities/UI
language through `x-webstudio-query` or another side channel.

- [x] Remove `x-webstudio-query` and all vendor query-definition
metadata from the Assets OpenAPI operation.
- [x] Resolve local OpenAPI references and derive the editable request
value, defaults, validation, labels, choices, arrays, objects, and
unions solely from standard operation parameters and request-body JSON
Schema.
- [x] Represent dynamic Assets field paths with standard JSON Schema
vocabulary in the `queryAssets` request schema.
- [x] Keep the normalized editor representation internal to
query-builder; do not expose another provider-authored capabilities
contract.
- [x] Remove provider-controlled layout metadata such as `group`; make
compact scalar packing and all other layout renderer policy.
- [x] Keep Webstudio expression binding as a consumer/editor concern
layered over schema values, not part of the OpenAPI contract.
- [x] Preserve the existing Assets filter, sort, output selection,
full-content/range selection, limit/offset, editable source, and
configured-field behavior.
- [x] Keep the Assets resource adapter responsible for serializing the
edited query into its request body.
- [x] Remove the obsolete Assets query-capabilities endpoint, URL, and
UI-side capability parsing.
- [x] Add a non-Assets OpenAPI proof with differently named parameters
and no vendor metadata.
- [x] Verify the generic packages contain no Assets imports, fixed
Assets property contract, or provider-authored layout model.
- [x] Update focused core, React, SDK, protocol/OpenAPI, Builder, and
source round-trip tests.

### 8. Manage ContentDatabase lifecycle in Builder

- [x] Cache databases by source revision plus normalized
compilation-plan hash.
- [x] Coalesce concurrent compilation requests.
- [x] Reuse cached database instances across field discovery,
autocomplete, query preview, and response preview when their normalized
compilation plans match.
- [x] Replace it when source revision or query requirements change.
- [x] Preserve the optional revision-keyed hosted parsed-document cache
without making it a source of truth.
- [x] Show exact preview warnings when concrete values reveal missing
bounded results.

### 9. Materialize and verify immutable builds

- [x] Compile and serialize the exact deployment-wide database during
prepublish validation.
- [x] Attach that validated bounded database to the immutable build
without rebuilding it from later asset state.
- [x] Show non-blocking prepublish warnings with database bytes,
omissions, and potentially affected resources.
- [x] Avoid claiming exact future route matches when dynamic values are
unknown.
- [x] Include the database/runtime only when a reachable configured
query requires them.
- [x] Ensure detail resources requesting Markdown content include all
statically eligible bodies needed by dynamic routes.

### 10. Integrate all targets

- [x] SSR: load the build's bounded database and conditionally include
the small runtime.
- [x] SSG: query the same database during generation, then omit
database/runtime output when no runtime query is needed.
- [x] Local/CLI: compile from the filesystem source with the same
semantics.
- [x] Preserve overview pagination, alternative route fields,
draft-as-user-data behavior, and full article rendering.
- [x] Verify ordinary Assets and fresh query-free sites perform no
parser, artifact, content-runtime dependency install, or cleanup work.

### 11. Correct API and package ownership

- [x] Move external Assets REST/OpenAPI contracts to
`@webstudio-is/protocol`.
- [x] Keep Webstudio domain models and Assets system-resource
configuration in the SDK.
- [x] Keep both cookie and token authentication behavior on the unified
Assets REST surface.
- [x] Remove pass-through route wrappers/reexports that add no transport
behavior.
- [x] Keep published runtime queries local to the artifact rather than
routing them through Builder REST.

### 12. Remove superseded deployment infrastructure

- [x] Remove external deployment-index upload, fetch, invalidation,
cleanup, and garbage-collection code.
- [x] Remove the SaaS asset-origin adapter requirement; close
webstudio-is/webstudio-saas#652 as superseded because it has no
independent purpose.
- [x] Remove index-bucket compatibility configuration and avoid adding
Infisical variables.
- [x] Remove obsolete external-index paths, package dependencies,
routes, scripts, and tests.
- [x] Consolidate unreleased intermediate migrations where safe and
retain only schema required by the final hosted cache/API design.

### 13. Verification and release readiness

- [x] Move and preserve existing tests while changing ownership; add new
tests only for changed contracts and behavior.
- [x] Run content-engine compiler/runtime tests, query-builder tests,
SDK/protocol/API tests, Builder tests, CLI tests, and affected
typechecks.
- [x] Run formatting, generated-file idempotence, fixtures, clean
Supabase-compatible migrations, E2E shards, and complete agent
evaluations.
- [x] Verify SSR and SSG conditional inclusion and query-free generated
package manifests.
- [x] Run the five-article MCP blog workflow: authoring, field
discovery, overview, dynamic detail routes, full Markdown rendering,
publishing, and published-site verification.
- [x] Run a maximum-budget database test and a
mutation-during-compilation test.
- [x] Perform a final architecture, security, API compatibility,
migration, bundle, correctness, and DRY review.
- [x] Reconcile every checkbox and replace the verification section with
results from the final architecture before release.

## Future MDX work

MDX is intentionally separate from this release.

- The compiler contract should allow a future build-only MDX parser.
- MDX compilation must not enter the published runtime.
- Embedded JSX must use an explicit compiled representation rather than
runtime source evaluation.
- Webstudio components, tokens, styles, and component allowlists remain
Webstudio integration concerns.
- The future MDX work should reuse `ContentArtifact` and
`ContentDatabase` where practical.

## Superseded design

The following earlier design choices are no longer the target
architecture:

- GROQ or GraphQL as the Assets query language.
- One external or long-lived index object per resource/query.
- A deployment index fetched by the Worker from SaaS/CDN infrastructure.
- Lazy production reads of source Markdown bodies.
- A separate index bucket, asset origin, invalidation service, or
garbage collector.
- A 4 MiB published index limit.
- Rebuilding an old build's database from the current mutable asset set.
- Bundling query infrastructure into projects without a configured
content query.

## Verification baseline

The embedded runtime-database architecture previously passed the checks
below. The OpenAPI-driven query-authoring change additionally passed:

- Query-builder core: 4 files / 19 tests.
- Query-builder React: 1 file / 7 tests.
- SDK Assets source/config: 1 file / 7 tests.
- Protocol Assets OpenAPI: 1 file / 9 tests, including independent
OpenAPI validation.
- Builder Assets OpenAPI description: 1 file / 5 tests.
- Query-builder, React, SDK, protocol, and Builder typechecks.
- Package-boundary validation and formatting of every affected file.

Local verification baseline:

- Content engine: 15 files / 104 tests.
- Asset uploader: 24 files / 175 tests.
- Builder: 204 files / 2,423 tests.
- CLI: 54 files / 866 tests.
- Complete workspace typecheck, formatting, lint, package boundaries,
generated fixtures, and fixture builds.
- The complete migration history applied successfully to a fresh
Supabase-compatible database, including every migration after the
consolidated Assets migration.
- Builder production build and full local E2E suite passed.
- Complete agent evaluations passed: authenticated page (170,859 ms),
design input (207,101 ms), and font assets (67,050 ms).
- Final review additionally verified correct REST edit/view/build
permits, cookie and public-token read paths, immutable snapshot retries,
exact database budgeting, query-free dependency omission, and SSR/SSG
conditional runtime inclusion.

Final staging verification passed against the embedded runtime-database
architecture: MCP uploaded and updated five Markdown articles,
discovered dynamic frontmatter fields, previewed a five-item sorted
overview and one full-body slug query, updated both Assets resources,
found zero binding issues, rendered `/blog` and `/blog/dynamic-routes`
at HTTP 200, published build version 266, and verified both published
routes at HTTP 200 with the expected article metadata and Markdown body.
The workflow also found and fixed offset-form asset timestamps failing
canonical datetime validation and made MCP `preview.start({ port: 0 })`
correctly select an available local port.

## Release constraints

- No new SaaS content service, bucket, or environment variable.
- No published-site database dependency.
- No parser/compiler dependency in query-free sites or runtime-only
bundles.
- The validated content database is immutable build output.
- Publishing warnings do not block deployment.
- The selected standard OpenAPI operation and its request JSON Schema
are the only source of query UI semantics.
2026-07-31 00:17:46 +02:00
Oleg Isonen 7bc9942774 perf: Improve MCP evaluation reliability and efficiency (#5959)
## Summary

- capture bounded MCP failure codes, validation issue paths, per-tool
latency, mutation timing, uncached input, catalog size, and
per-call/per-tool response bytes in every evaluation result
- require every raw fixture outcome to have zero failed calls and zero
retries, even when the workflow later recovers
- update baselines only when all raw outcomes and all regression
comparisons pass
- refuse to replace an accepted aggregate token baseline with a noisier,
higher-token sample
- regression-gate aggregate response bytes alongside tokens, calls,
focused reads, mutations, errors, and retries
- compact repeated SDK schemas while preserving required arguments,
destructive safety controls, and ordinary optional fields for common
goal-guided tools
- omit low-frequency optional argument names until focused discovery,
reducing the initial tool handshake without removing callable tools
- stop echoing the caller's goal from `meta.guide` responses
- make evaluations copy their canonical objective verbatim into
`meta.guide`, preventing vague paraphrases from selecting a generic
workflow
- normalize grouped `styles` and `declarations` maps into canonical flat
`update-styles` operations before validation
- normalize relative `create-page` paths through the platform URL parser
- bundle the four bounded authentication discovery reads into one
`inspect-auth-context` call
- bound large MCP style-key mutation results while preserving a
representative sample and exact total count
- combine fragment insertion with immediate persisted binding
verification to remove a sequential model turn

## Root causes and impact

Recovered MCP failures could previously be reported as successful raw
outcomes, and baseline files could be written before the complete
comparison result was known. Raw outcomes now fail on any MCP error or
retry, and accepted baselines are written only after every fixture and
comparison passes.

Rejected samples exposed two concrete error sources. A vague
authentication objective selected a generic workflow and produced four
validation failures, 20 calls, and 1.07M tokens; evaluations now pass
the canonical objective verbatim. A relative `create-page.path` caused
an `INVALID_INPUT` failure and retry; the adapter now canonicalizes it
before validation.

The deterministic catalog in the accepted baseline measures **50,864
bytes**, down from **53,317 bytes** (−4.60%). Input schemas fell from
**31,190 to 28,737 bytes** (−7.86%). The two composite workflows add two
conditionally exposed tools and 360 catalog bytes (about +0.71%) when
their backing operations are available.

Broad experiments that removed focused descriptions or all goal-specific
examples made guide responses smaller but increased token use or caused
retries. Those variants were rejected. The current implementation
compacts only recognized goal guides, while retaining the detailed
`update-styles` schema and the `upload-assets` batch example proven
necessary by the evaluations.

## Workflow-churn iteration

Authenticated discovery uses one model-visible `inspect-auth-context`
call. Fragment authoring can now use `insert-fragment-verified`, which
commits the insertion and immediately runs persisted binding
verification for up to 200 bindings. This removes the following
sequential verification turn without changing mutation or verification
counts.

Current deterministic workflow shape:

- authenticated workflow calls: **14 → 10** (−28.57%)
- design workflow calls: **17 → 16** (−5.88%)
- aggregate workflow calls: **39 → 34** (−12.82%)
- failures: **0**
- retries: **0**
- authenticated MCP response bytes: **52,524 → 46,900** (−10.71%)
- authenticated guide response: **26,782 → 24,358 bytes** (−9.05%)

One complete run measured authenticated tokens at **348,225** (−11.44%)
and first verification at **38.3s** (−30.14%); design first verification
moved to **28.1s** (−27.27%). The final exact-code run also passed with
**10/16/8** calls and zero failures/retries, measuring authenticated
tokens at **377,802** (−3.92%) and design first verification at
**34.5s** (−10.67%).

The composite preserves post-commit safety: if verification
infrastructure fails after insertion, the result remains
`committed:true`, returns `POST_COMMIT_VERIFICATION_FAILED`, and
explicitly instructs the agent not to repeat the insertion. A guarded
baseline-update run was rejected because stochastic aggregate tokens did
not beat the accepted result, so no result artifacts were rewritten.

## Style response compaction

Large style mutations previously returned every generated style key. In
the design fixture, that single successful response was **19,121 bytes**
and remained in context for all later turns. MCP responses now retain
the first 20 keys plus `styleKeyCount` and `styleKeysTruncated`; the
typed runtime operation remains unchanged.

Focused evaluation results:

- `update-styles` response: **19,121 → 2,815 bytes** (−85.28%)
- design-workflow MCP responses: **70,941 → 54,633 bytes** (−22.99%)
- calls: **17 → 17**
- failures/retries: **0 / 0**
- tokens: **498,764 → 505,136** (+1.28%, treated as noise and not
accepted as a new baseline)

The subsequent complete `pnpm evaluations` run passed all three fixtures
with exact **11/17/8** call sequences and zero failures/retries. The
accepted lower-token baseline remains unchanged.

## Goal-guide context compaction

Recognized goal guides no longer repeat every selected tool's
description and examples. Current Codex clients defer MCP tools and load
each named tool's exact contract before invocation, so the guide keeps
the goal-specific workflow while returning compact tool references.
Free-form guide searches remain fully descriptive.

Two complex inputs remain intentionally hydrated: `update-styles` keeps
its detailed schema, and `upload-assets` keeps one batch example.
Removing the upload example caused five failed attempts in a complete
run; restoring only that example returned the font workflow to its exact
8-call, zero-error path.

Final complete-suite results:

- calls: **10 / 16 / 8** (**34 aggregate**)
- failures/retries: **0 / 0**
- goal-guide payload: **58,082 → 37,718 bytes** (−35.06%)
- authenticated MCP responses: **46,900 → 38,268 bytes** (−18.41%)
- design MCP responses: **51,854 → 44,008 bytes** (−15.13%)
- aggregate MCP responses versus the accepted baseline: **147,335 →
101,922 bytes** (−30.82%)
- aggregate tokens: **1,146,951**, only **+0.33%** above the stricter
accepted 1,143,217-token baseline

The complete suite and every per-fixture comparison passed. The guarded
updater did not replace the accepted artifacts because the aggregate
token sample did not beat the existing baseline.

## Responsive verification batching

The MCP now exposes `screenshot.responsive` when reusable page capture
is available. It accepts one generated route plus 1–8 viewports,
refreshes the session preview once, reuses one browser target, and
returns bounded evidence for every viewport. Auth and design evaluation
workflows use one responsive capture instead of a separate preview start
and two screenshot turns.

Automatic screenshots now allocate an available preview port and reuse
an already-running managed preview port. This prevents an unrelated
service on the historical 5173 default from producing an MCP failure.
Explicit ports, local previews, and external URLs remain unchanged.

The evaluator records the bounded viewport array, counts the batch as
verification and visual evidence, and compares its latency, response
size, calls, errors, retries, and token consumption on every run. The
new regression tests were demonstrated fail-then-pass.

Guarded accepted results:

- aggregate tokens: **1,143,217 → 990,190** (−13.39%)
- aggregate calls: **39 → 30** (−23.08%)
- aggregate MCP response bytes: **147,335 → 98,470** (−33.17%)
- failures/retries: **0 / 0**
- authenticated calls: **14 → 8** (−42.86%)
- design calls: **17 → 14** (−17.65%)

## Design and font workflow batching

Design discovery now uses one conditionally exposed
`inspect-design-context` call to return bounded pages, breakpoints,
design tokens, assets, and variables instead of five model-visible
reads. Font persistence now uses one `verify-font-assets` call to
refresh the asset namespace and return every changed font asset instead
of a refresh plus separate verification reads.

The evaluation tasks and validators require these composite paths. Both
tools retain the underlying operation limits, namespace metadata,
persisted-result checks, and zero-error/retry gates. Review also
confirmed that SDK schemas must keep `additionalProperties:false` and
that non-default MCP safety hints must remain explicit; those protocol
constraints were preserved rather than traded for catalog-token savings.

Guarded accepted improvements versus the previous responsive-batching
baseline:

- aggregate tokens: **990,190 → 914,760** (−7.62%)
- aggregate calls: **30 → 24** (−20.00%)
- aggregate focused reads: **8 → 2** (−75.00%)
- aggregate MCP response bytes: **98,470 → 95,614** (−2.90%)
- design calls: **14 → 10** (−28.57%)
- font calls: **8 → 6** (−25.00%)
- failures/retries: **0 / 0**

The strict SDK catalog is **51,362 bytes** with **28,964 input-schema
bytes**. The two new conditional workflow tools add 355 catalog bytes
(+0.70%) and 130 schema bytes (+0.45%) while removing six model-visible
workflow turns.


## Terminal responsive verification

Auth and design now finish with one conditionally exposed
`verify-page-responsive` call. It captures every requested viewport in
one browser session and immediately runs the static page audit, removing
one sequential model turn per workflow while retaining both kinds of
evaluator evidence. The tool inherits the audit operation's permit and
read namespaces; strict SDK schemas and MCP safety annotations remain
unchanged.

Latest complete comparison against the accepted baseline:

- aggregate tokens: **914,760 → 869,274** (−4.97%)
- aggregate calls: **24 → 22** (−8.33%)
- aggregate MCP response bytes: **95,614 → 95,053** (−0.59%)
- authenticated calls: **8 → 7**
- design calls: **10 → 9**
- font calls: **6 → 6**
- failures/retries: **0 / 0**
- authenticated focused run: **289,078 tokens** (−10.06%) and first
verification **55,552 ms** (−17.90%)
- design focused run: p95 MCP latency **9,642 ms** (−10.32%); token and
first-verification variance increased in that individual sample, so only
the complete comparison is used for the aggregate conclusion

The complete comparison passed all fixture and aggregate regression
gates. Two subsequent guarded `--update-baselines` attempts could not
start any fixture because the external Codex runner had reached its
usage limit; both exited before the first MCP call and wrote no accepted
result files. Therefore the tracked accepted baseline below remains
**914,760 tokens / 24 calls**, while **869,274 tokens / 22 calls** is
the latest successful complete comparison.

Regression coverage was demonstrated fail then pass. After this
iteration, project-build MCP tests passed **105/105**, high-impact
evaluation tests passed **41/41**, and project/CLI typechecks, lint,
package boundaries, generated docs, and diff checks all passed.

## Accepted evaluation baseline

| Fixture | Failed | Retries | Calls | Mutations | Tokens | Uncached
input | Response bytes | First mutation | First verification |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| authenticated-page-v1 | 0 | 0 | 8 | 4 | 321,396 | 44,353 | 37,536 |
39,076 ms | 67,660 ms |
| design-input-v1 | 0 | 0 | 10 | 6 | 330,580 | 54,441 | 40,036 | 30,419
ms | 36,551 ms |
| font-assets-v1 | 0 | 0 | 6 | 3 | 262,784 | 20,704 | 18,042 | 37,609 ms
| 47,073 ms |

Accepted aggregate: **914,760 tokens**, **24 calls**, **2 focused
reads**, and **95,614 MCP response bytes**, with zero failures and zero
retries. Compared with the previous accepted baseline, tokens fell
**7.62%**, calls fell **20.00%**, focused reads fell **75.00%**, and
response bytes fell **2.90%**.

## Verification

- [x] Complete `pnpm evaluations -- --update-baselines`
- [x] Independent complete `pnpm evaluations` after the composite
workflow change
- [x] Worse-token and error-containing samples rejected without
replacing accepted baselines
- [x] Added/changed regression tests demonstrated fail then pass
- [x] Project-build MCP tests: 105 passed
- [x] Complete high-impact evaluation tests: 41 passed
- [x] Project-build and CLI typechecks
- [x] `pnpm lint`
- [x] `pnpm check:package-boundaries`
- [x] `pnpm check:generated-docs`
- [x] `git diff --check`
- [x] Earlier workspace tests, including 2,356 builder tests
- [x] Earlier generated API checks
- [ ] Fixture sync/build: sync could not reach
`main.development.webstudio.is` and timed out; no local test,
evaluation, or code check failed

## Implementation checklist

- [x] Error and retry rate enforced at zero for raw and compared
outcomes
- [x] Relative page paths normalized to prevent a known validation
failure and retry
- [x] Baseline replacement blocked unless every outcome and comparison
passes
- [x] Aggregate token baselines cannot ratchet upward
- [x] Workflow churn bounded and regression-gated
- [x] Authentication discovery collapsed from four model-visible reads
to one
- [x] Fragment insertion and persisted binding verification combined
safely
- [x] Large style-key mutation results bounded at the MCP boundary
- [x] Token consumption captured, compared, and regression-gated
- [x] Response bytes captured per call and tool, summarized, and
aggregate regression-gated
- [x] Per-tool latency, mutation timing, catalog size, and validation
paths captured
- [x] Repeated catalog/schema and focused-guide context reduced
- [x] Canonical goal selection stabilized for evaluations
- [x] Grouped style maps normalized before validation
- [x] Responsive verification batches desktop and mobile evidence in one
call
- [x] Automatic screenshot previews avoid occupied default ports
- [x] Responsive viewport evidence is captured and regression-gated
- [x] Accepted evaluation artifacts preserve the best measured token
baseline
2026-07-30 11:34:02 +02:00
Oleg Isonen e80ca3b54c fix: Improve MCP authoring and screenshot reliability (#5957)
Closes #5954
Closes #5951
Closes #5950
Closes #5953

## Summary

- route external screenshot `url` and `baseUrl` targets through the
self-closing browser path
- keep reusable Chromium sessions limited to Webstudio-managed session
previews
- validate typed style values at the public runtime boundary while
retaining CSS strings for design tokens
- build MCP and shell validation messages from compact structured issues
- advertise typed style objects with a concrete keyword example
- persist `update-asset` metadata patches to the related file record and
verify the saved value
- store multi-sentence page titles and metadata as fixed text without
misclassifying sentence punctuation as member access
- capture privacy-safe aggregate token usage and MCP workflow metrics in
every high-impact evaluation result
- compare each evaluation with a compatible checked-in baseline and fail
on correctness, token, tool-use, error/retry, broad-read, or MCP-latency
regressions

## Root causes

### External screenshot lifecycle

The fast iterative preview change made `session` the default preview
source. External captures were then treated as managed session-preview
captures and opened a reusable Chromium session. One-shot MCP processes
do not call `preview.stop`, so the browser kept the process alive and
could be orphaned when the caller killed the hung CLI.

### Validation errors and schemas

`update-styles` declared `value` as unconstrained input, and
design-token objects were checked only for a string `type`. Invalid
values therefore passed the advertised/input schema and failed later in
the full `StyleValue` parser, exposing the raw Zod union message. Error
serialization already carried flattened issues but did not use them for
`error.message`.

### Asset metadata persistence

`update-asset` correctly produced an `assets/[id]/meta` patch, but asset
persistence compared only Asset-table fields. Metadata lives on the
related File row, so the patch was treated as unchanged and the
successful mutation never wrote the new value.

### Page text expression disambiguation

Page text normalization treated a period followed by whitespace and a
word as JavaScript member access. Normal prose such as `Marques. Toutes
nos marques` was therefore validated as an expression and rejected
instead of being stored as a string literal.

### Evaluation observability

The evaluation result only stored duration and two call counts, so token
cost, failures, retries, mutation flow, verification coverage, and MCP
latency could regress without a durable comparison. Results now use a
bounded schema-v2 aggregate and never persist prompts, transcripts,
stdout/stderr, credentials, arguments, or response payloads.

## Impact

- external screenshots complete without retaining browser processes
- repeated managed-preview screenshots retain browser reuse
- invalid style inputs fail at the public boundary with paths such as
`updates.0.value`
- `error.message` is concise and actionable instead of a repeated raw
union dump
- generated schemas no longer advertise primitive or array values for
`update-styles`
- supported design-token CSS strings remain accepted
- `update-asset` font metadata remains changed after commit, refresh,
and reload
- `create-page` and `update-page` accept ordinary multi-sentence titles
and SEO descriptions while preserving computed expressions
- every high-impact run reports input, cached-input, cache-write,
output, reasoning-output, total-token, and cache-rate aggregates
- MCP results report calls, success/failure, retries, focused/broad
reads, dry runs, planned/committed mutations, verifications,
total/p50/p95 latency, and first-mutation/verification timing
- compatible results are compared with accepted baselines using a 15%
relative regression threshold; correctness failures and zero-baseline
errors, retries, or broad reads remain zero-tolerance
- stochastic wall-clock and milestone timings remain visible as deltas
without making a single noisy run fail

## Todo

- [x] Reproduce external URL browser-session routing
- [x] Use self-closing capture for external screenshots
- [x] Preserve browser reuse for managed session previews
- [x] Reproduce primitive style-value schema acceptance
- [x] Validate typed style values at the public boundary
- [x] Preserve CSS-string design-token inputs
- [x] Format stdio and shell validation messages from structured issues
- [x] Regenerate and verify runtime operation contracts
- [x] Reproduce dropped asset metadata at the persistence boundary
- [x] Persist and verify related File metadata updates
- [x] Reproduce multi-sentence page text failures on create and update
- [x] Distinguish sentence punctuation from computed member access
- [x] Add privacy-safe token and MCP workflow metrics
- [x] Add per-fixture baseline comparisons and regression gates
- [x] Refresh accepted schema-v2 baselines for all fixtures
- [x] Review the complete diff
- [x] Run repository checks and the complete agent evaluation suite

## Verification

- `pnpm checks` — passed, including workspace tests, typechecks, lint,
package-boundary checks, generated API/docs checks, fixture sync, and
fixture builds
- builder tests — 189 files, 2,356 tests passed
- CLI tests — 53 files, 852 tests passed
- project-build tests — 81 files, 2,023 tests passed
- asset-uploader tests — 11 files, 98 tests passed
- targeted evaluation metrics/regression/trace/runner tests — 5 files,
17 tests passed
- all four issue regression tests and the new evaluation harness tests
were demonstrated failing without their implementations and passing with
them
- `pnpm evaluations -- --update-baselines` — 3/3 fixtures passed and
schema-v2 baselines were updated
- a subsequent real `font-assets-v1` result compared successfully after
the regression policy was finalized
2026-07-29 19:33:24 +02:00
Oleg Isonen 5141f61f75 fix: prevent mixed router dependencies in MCP previews (#5948)
## Summary

- generate MCP previews from the standalone React Router template
- prevent Remix and React Router runtimes from being merged into one
dependency graph
- keep the normal Remix build path unchanged

## Root cause

The preview generator combined the complete React Router template with
the Remix defaults template. That installed React Router 7 alongside
Remix-provided React Router DOM 6, causing Vite to fail on incompatible
exports before the iterative preview server became ready.

## User impact

`preview.start({ source: "session", mode: "iterative" })` can now start
successfully, so agents can reuse the preview for fast edit, reload,
screenshot, and vision cycles.

## Verification

- [x] Generated a preview from production-size project data and received
HTTP 200
- [x] Verified the generated package contains React Router and no Remix
router dependencies
- [x] 55 targeted CLI preview, screenshot, and MCP preview tests
- [x] CLI typecheck and formatting
- [x] `pnpm checks`
- [x] `pnpm evaluations` (all three real agent fixtures passed)
2026-07-24 15:18:05 +02:00
Oleg Isonen b2a34f1713 feat: Fast MCP iterative previews (#5947)
## Summary

This PR documents a measured architecture plan for making the MCP
edit-to-screenshot loop fast enough and reliable enough for iterative
site authoring. It intentionally changes no runtime behavior yet.

The current implementation rebuilds a production preview after small
committed mutations. Testing an editable local clone of the production
Webstudio homepage also exposed a correctness gap: a path screenshot
called directly after a mutation can return a stale, byte-identical
image.

## Measured baseline

Repository fixture:

- committed text edit: **24-25 ms**
- post-edit regeneration, production build, and server restart: **7.9
s**
- first screenshot after restart and browser relaunch: **9.1 s**
- combined first post-edit screenshot: **17.1 s**
- unchanged warm screenshot: **1.1 s**

Production homepage, read-only:

- remote refresh: **3.1 s**
- complete production preview preparation/start: **16.0 s**
- cold homepage screenshot: **10.2 s**
- unchanged warm screenshot: **1.36 s**
- predicted post-edit screenshot from measured phases: **approximately
26.2 s**

Editable local production-homepage clone:

- retained **43 routes** and **266 homepage text nodes**
- committed heading mutation: **0.81-0.87 s**
- explicit post-edit preview generation/build/start: **11.6-12.6 s**
- first screenshot after explicit preview start: **9.8-11.8 s**
- complete mutation-to-visible-screenshot cycle: **22.9-25.2 s**
- complete edit, verify, screenshot, restore, and stop run: **52.7 s**

The changed screenshot visibly rendered the test heading, and the
restored screenshot matched the original byte for byte.

## Correctness finding

Calling a path screenshot directly after the committed mutation took
**20.1 s**, but both post-edit captures were byte-identical to the
baseline and rendered the old heading. Explicitly calling
`preview.start({ source: "session" })` made the edit visible.

The reported generated-build version also remained at 1 while the
project session advanced through versions 4 and 5. Current screenshot
output therefore cannot prove that it rendered the mutation it is meant
to verify.

## Proposed direction

- [ ] Introduce explicit iterative and production preview modes under
one controller contract.
- [ ] Keep a Vite development preview and browser alive for MCP
authoring screenshots.
- [ ] Preserve production preview for rendered audits and final
verification.
- [ ] Regenerate from the authoritative project session, wait for Vite
compilation, then normally reload the retained browser page.
- [ ] Make a direct post-mutation path screenshot refresh the latest
committed session without using HMR.
- [ ] Add a project-version freshness handshake so stale screenshots
fail.
- [ ] Serialize and coalesce regeneration after rapid mutations.
- [ ] Return preview mode, rendered project version, and complete phase
timings.
- [ ] Add lifecycle, integration, and local agent evaluation coverage.

## Performance target

Warm edit-to-screenshot latency should be at most **3 seconds p95**
across ten sequential small edits, without invoking `npm run build`,
`npm run start`, or launching another browser.

## Verification

- [x] Measured the source CLI against the authenticated repository
fixture.
- [x] Measured the production homepage without changing production.
- [x] Synchronized the production bundle into a disposable local Builder
project.
- [x] Committed and visually verified a real heading edit through local
MCP.
- [x] Restored the original heading and verified the restored screenshot
byte for byte.
- [x] Reproduced the stale direct-screenshot behavior.
- [x] Formatted the plan with `oxfmt`.
- [x] Verified the documentation diff with `git diff --check`.

The complete architecture, race handling, test matrix, acceptance
criteria, and non-goals are in
`apps/builder/docs/mcp-iterative-preview-plan.md`.
2026-07-24 01:05:10 +02:00
9a087c573a fix: use local URLs for expression assets in exports (#5945)
Fixes #5763

## Description

Assets selected directly in a project already use the generated app's
configured asset base. Assets referenced through `assets.data[...]`,
however, were still emitted with builder-only `/cgi/` URLs, so those
references broke in Docker and other self-hosted exports.

This makes generated asset resources use the same local asset base as
direct asset props while keeping their runtime metadata. The live
builder continues to use its existing `/cgi/` routes.

## Steps for reproduction

1. Upload an audio file and reference `assets.data["<asset-id>"].url`
from the expression editor.
2. Generate a self-hosted project.
3. Inspect `app/__generated__/$resources.assets.ts`.
4. The asset URL is now `/assets/<filename>` instead of the unavailable
`/cgi/asset/<filename>?format=raw` route.

## Testing

- `pnpm --filter webstudio test -- src/prebuild.test.ts -t "uses the
local asset base"`
- `pnpm --filter webstudio exec vite build`
- `pnpm exec oxfmt --check packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts`
- `pnpm exec oxlint --deny-warnings packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts`

## Code Review

@kof, could you please:

- [ ] Review the generated asset URL behavior
- [ ] Review the implementation and regression test
- [ ] Verify an expression-based asset in a self-hosted preview

## Before requesting a review

- [x] Made a self-review
- [x] Added a comment explaining why generated exports override the
runtime URL
- [x] Added a regression test

---------

Co-authored-by: Sviat Barbutsa <9897782+sviat-barbutsa@users.noreply.github.com>
Co-authored-by: Oleg Isonen <oleg008@gmail.com>
2026-07-23 00:11:16 +02:00