6624 Commits
Author SHA1 Message Date
Oleg Isonen 9cc23be76d build: Release 19-08-2026 0.293.0 2026-08-19 01:29:22 +01:00
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 5d10a7de60 fix: Recover responsive screenshots after DevTools disconnects (#6170)
## Summary

Recover responsive screenshot batches when the Browser DevTools
WebSocket closes transiently.

The capture session now treats a DevTools socket closure as the same
recoverable session failure as an unexpected browser exit. It restarts
Chromium once and reruns the complete viewport batch, so callers receive
a consistent set of screenshots instead of partial artifacts.

## Root cause

The DevTools socket close handler rejected pending commands with a
generic `Error`. The reusable browser retry boundary only recovered
typed browser-session closures or a browser process that had exited, so
a closed socket with a still-running Chromium process escaped as
`MCP_TOOL_FAILED`.

## Technical choices

- Classify a DevTools WebSocket closure as `BrowserSessionClosedError`
at the CDP boundary.
- Reuse the existing bounded browser-session restart path instead of
adding a separate MCP-layer retry.
- Retry the complete responsive batch once so results remain
all-or-nothing.
- Preserve the original `Browser DevTools connection closed.` diagnostic
when the replacement connection also closes.

## Impact

A transient DevTools disconnect during a responsive capture
automatically restarts the reusable browser and captures every requested
viewport. Repeated connection loss still fails after one retry without
looping indefinitely.

## Checklist

- [x] Add a fail-first regression for a two-viewport responsive capture
- [x] Verify mobile and desktop widths are both recaptured after
reconnect
- [x] Verify a second consecutive disconnect returns the original error
- [x] Independently verify recovery with real Chrome, CDP, rendering,
and PNG output
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected
- [x] Review documentation impact: no user-facing documentation changes
are required because this restores documented behavior
- [ ] Run agent evaluations (requires separate approval)

## Verification

- New responsive reconnect regression failed before the implementation
with `Browser DevTools connection closed.` and passes after it
- Independent real-browser harness with Google Chrome `151.0.7922.138`:
- served a real local responsive page and captured `375×812` and
`1440×900`
- forcibly closed the first real CDP WebSocket during
`Page.captureScreenshot`
- observed Chrome #1 terminate, its profile get removed, Chrome #2
launch, and the complete viewport batch replay
- produced valid PNGs at both requested dimensions and left no browser
processes or temporary profiles behind
- `pnpm --filter @webstudio-is/vision test` — 13 files, 81 tests passed
- `pnpm --filter @webstudio-is/vision typecheck`
- `pnpm --filter webstudio exec vitest run
src/commands/mcp-preview.test.ts` — 43 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings
packages/vision/src/screenshot-browser-cdp.ts
packages/vision/src/screenshot-browser-cdp.test.ts`
- `pnpm exec oxfmt packages/vision/src/screenshot-browser-cdp.ts
packages/vision/src/screenshot-browser-cdp.test.ts`
- `git diff --check`

The real-browser fault was injected deterministically at the first
screenshot command rather than produced by an organic OS or network
failure. It exercised the reported DevTools closure boundary and
complete responsive-batch recovery.

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

Closes #6169
2026-08-18 12:53:50 +02: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 ab5717c9cf feat: Support CSS properties of every status (#6166)
## Summary

- Make every CSS property in the current MDN dataset available in the
Advanced style workflow, regardless of standards status.
- Mark experimental, nonstandard, and obsolete properties with a yellow
warning icon, status-specific tooltip, and MDN link.
- Add complete CSS anchor-positioning property and value support.
- Update `mdn-data` from 2.28.0 to 2.33.0 and `css-tree` from 3.1.0 to
3.2.1.

## Root cause

Webstudio generated its supported property list from MDN data but
filtered by status and then manually allowed a few experimental and
nonstandard entries. A property could therefore exist in the installed
MDN package without being exposed by the Builder.

Runtime validation had a separate grammar gap. CSS-tree 3.2.1 now
includes the anchor functions and alignment values, but its bundled data
predates the current `position-anchor: normal | match-parent` additions.

## Implementation

- Remove status-based property filtering and the nonstandard-property
allowlist.
- Generate one property-to-status map for 486 standard, 40 experimental,
119 nonstandard, and 15 obsolete properties.
- Show status-specific browser-compatibility guidance for every
non-standard status. Active declarations keep the warning after the
value with 3px left spacing.
- Infer MDN links from existing property metadata and fall back to MDN
search for entries without a direct URL.
- Preserve known vendor prefixes from the generated property inventory
instead of maintaining a prefix allowlist.
- Generate WebKit text fill/stroke longhands directly from MDN instead
of preserving them through parser exceptions.
- Preserve unsupported initial-value shapes as unparsed CSS rather than
excluding their properties.
- Use CSS-tree 3.2.1's native anchor-function and alignment grammars.
Keep one narrow extension for the current `position-anchor` keywords
missing from its bundled MDN data.
- Generate recursively flattened shorthand longhands and initial values
from MDN.
- Expand syntax-defined shorthands generically from CSS-tree's matched
property captures, including function-valued components.
- Decline generic expansion when CSS-tree and MDN disagree about the
longhand set or a longhand repeats.
- Update the font grammar reference and CSS math validation for CSS-tree
3.2.1 compatibility.
- Regenerate CSS metadata and property-variable fixtures.

## Checklist

- [x] Include MDN properties regardless of status.
- [x] Generate status metadata for standard, experimental, nonstandard,
and obsolete properties.
- [x] Show warning icons in Add styles results and active declarations.
- [x] Add status-specific consequences and MDN links.
- [x] Preserve generated vendor-prefixed properties during CSS import.
- [x] Generate WebKit text fill/stroke longhands without parser
exceptions.
- [x] Support `anchor()`, `anchor-size()`, and `anchor-center`.
- [x] Support current `position-anchor` keywords.
- [x] Expand syntax-compatible shorthands through generic MDN metadata
and CSS-tree captures.
- [x] Cover ordered optional, unordered, slash-separated,
function-valued, omitted, CSS-wide, variable, invalid, and
incompatible-data cases.
- [x] Verify new status, warning, prefix, and shorthand tests fail
without their implementations and pass when restored.
- [x] Update to CSS-tree 3.2.1.
- [x] Regenerate CSS metadata and property-variable fixtures.
- [x] Review documentation impact; no authoritative Builder
documentation lists individual supported CSS properties, so no update is
needed.
- [x] Review fixture impact; 668 fixtures and 2,030 valid cases were
regenerated with zero unsupported.

## Verification

- `pnpm --filter @webstudio-is/css-data test` — 4,618 passed, 1 skipped.
- Builder CSS editor and property-status tests — 25 passed.
- CSS data, CSS engine, Project Build, and Builder typechecks passed.
- Lint, formatting, repeated regeneration, and diff checks passed.

## Known limitation

This change covers CSS declarations and values. It does not add a
dedicated Builder editor for the `@position-try` at-rule.

Closes #6162
2026-08-17 14:54:11 +02: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 95c867697f feat: Make MCP verification proportional (#6155)
## Summary

Make MCP verification proportional to the requested task and require an
explicit preflight before a full production preview starts.

This is the first vertical slice of the broader fast-workflow work in
#6154. It fixes the current guidance and production-preview escalation
behavior without claiming the universal search, cross-record update, or
targeted route-evaluation APIs are complete.

## Technical choices

- Classify `meta.guide` requests as:
  - small value or reference correction
  - focused page change
  - visual change
  - structural project change
  - project-wide migration
- Give small corrections the explicit workflow `Focused search → atomic
edit → targeted assertions` and omit the visual loop.
- Include full-production-preview cost and faster-alternative metadata
in goal guidance.
- Change mutation follow-up guidance so focused assertions are
sufficient for small corrections; reserve audit and screenshot work for
structural, visual, or runtime-dependent outcomes.
- Add `maxDurationMs`, `confirmSlow`, and `confirmationToken` to
`preview.start`.
- Return a non-running preflight for production mode with an honest
30–60 second estimate, purpose, faster alternative, and short-lived
token.
- Bind confirmation tokens to the normalized preview input and time
budget so changed requests require a fresh preflight.

## Tradeoffs and compatibility

- Iterative preview behavior is unchanged.
- Production preview now requires one additional confirmed call.
- The successful `preview.start` output still reports running state and
mode; `url` is optional because a preflight has not started a server.
- This PR does not yet add universal content search, match IDs,
cross-kind atomic updates, or a targeted route renderer. Those remain in
#6154.

## Todo

- [x] Classify MCP task scope in `meta.guide`
- [x] Recommend focused search → atomic edit → targeted assertions for
small corrections
- [x] Stop prescribing preview/screenshots for non-visual small
corrections
- [x] Add production-preview time-budget preflight
- [x] Require an unchanged short-lived confirmation token before
production preview
- [x] Add fail-before/pass-after regression coverage
- [x] Review documentation impact: generated MCP docs are unchanged; no
authored `docs/` update is required for this internal contract increment
- [ ] Add universal search results with stable match IDs and
route/location context
- [ ] Add one cross-kind atomic semantic update operation
- [ ] Add lightweight route assertions and targeted route rendering
- [ ] Apply the generic slow-operation contract to other broad audits,
crawls, and multi-capture workflows
- [ ] Add historical timing inputs to cost estimates

## Verification

- `pnpm --filter @webstudio-is/project-build test -- --run
src/mcp.test.ts` — 116 passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm exec oxlint --deny-warnings packages/project-build/src/mcp.ts
packages/project-build/src/mcp.test.ts`
- `pnpm docs:mcp:sync` — no generated diff
- `pnpm docs:mcp:test` — 3 passed
- `pnpm docs:mcp:typecheck`
- Agent evaluations were not run because they require separate explicit
approval.

Ref #6154
2026-08-15 15:10:35 +02:00
Oleg Isonen cdbe9d99f5 fix: Accept custom variable font axes (#6152)
## What changed

- Replace the finite variable-font axis allowlist with validation for
OpenType’s four-character axis tag format.
- Preserve both registered axes and foundry-defined custom axes such as
`CNTR` in font metadata.
- Regenerate the runtime operation contracts so API consumers receive
the new axis-tag constraint.
- Refresh all project fixture bundle versions for the changed public
contract.
- Add regression coverage for a variable font metadata payload
containing `wght` and `CNTR`.

## Root cause

`fontkit` successfully parsed the supplied Antarctica WOFF2 files and
returned all four axes. Webstudio’s shared font schema then rejected
`CNTR` because the schema enumerated only known registered axes and a
small set of Roboto Flex custom axes. That validation failure surfaced
through the upload route as “Internal assets API error”.

The updated schema validates the OpenType axis-tag shape instead of
attempting to enumerate valid tags, so future registered axes and
conforming foundry-defined axes do not require code changes.

## User impact

Valid variable fonts with custom variation axes can be uploaded without
pinning or removing those axes. Existing static fonts and variable fonts
using standard axes keep the same behavior.

## Checklist

- [x] Reproduce the schema rejection with a `CNTR` regression test.
- [x] Confirm the regression test fails before the implementation change
and passes after it.
- [x] Validate the supplied passing Antarctica font and both previously
failing `CNTR` variants through `fontkit` and the updated production
schema.
- [x] Run the complete `@webstudio-is/fonts` test suite.
- [x] Run the complete `@webstudio-is/asset-uploader` test suite.
- [x] Run typechecks for `@webstudio-is/fonts`, `@webstudio-is/sdk`, and
`@webstudio-is/asset-uploader`.
- [x] Regenerate and verify runtime API contracts.
- [x] Refresh and build all project fixture bundles for the new contract
version.
- [x] Run the repository linter.
- [x] Review documentation impact: no documentation update is needed
because this restores support for valid input within the existing
variable-font upload behavior.

## Verification

- `pnpm --filter @webstudio-is/fonts test` — 10 tests passed
- `pnpm --filter @webstudio-is/asset-uploader test` — 196 tests passed
- `pnpm --filter @webstudio-is/protocol test` — 91 tests passed
- `pnpm --filter @webstudio-is/fonts typecheck`
- `pnpm --filter @webstudio-is/sdk typecheck`
- `pnpm --filter @webstudio-is/asset-uploader typecheck`
- `pnpm check:generated-api`
- `pnpm -r fixtures:build`
- `pnpm lint`
- Local supplied-font check: `PASS-antarctica-without-CNTR.woff2`,
`FAIL-antarctica-with-CNTR.woff2`, and
`FAIL-antarctica-raw-rebuild-with-CNTR.woff2` all validate with complete
axis metadata. The commercial binaries are not committed.

The full `pnpm fixtures` command reached the remote sync phase but could
not complete because `main.development.webstudio.is` still advertises
the pre-change bundle contract. The local fixture build and
compatibility tests pass with the refreshed version.

Closes #6151
2026-08-15 00:29:51 +02:00
Oleg Isonen a836f29a69 fix: Explain asset count limits (#6149)
## Summary

Return a specific, safe error when a project reaches its plan's
asset-count limit instead of reporting an internal Assets API failure.

## Root cause

The uploader enforced `maxAssetsPerProject` by throwing a plain `Error`.
The REST boundary correctly treated unknown errors as internal failures,
returned HTTP 500, and redacted the useful limit explanation. The
Builder then displayed that generic response.

## Technical choices

- Represent the expected quota condition with
`AssetUploadCountLimitError`.
- Map that domain error to HTTP 409 while preserving its existing
message.
- Keep the generic HTTP 500 response and server logging for unexpected
failures.
- Reuse the Builder's existing API-error handling; it already displays
the returned `errors` value in the upload toast.

## Checklist

- [x] Reproduce the generic uploader and REST error behavior
- [x] Add regressions and verify they fail before the behavior change
- [x] Throw a typed error when the asset-count limit is reached
- [x] Return the safe limit explanation through the Assets API
- [x] Verify unexpected internal errors remain redacted
- [x] Review documentation impact — no documentation change is needed
for this error-message correction
- [x] Review fixture impact — no fixture inputs or generated outputs are
affected
- [x] Review the final diff for unrelated changes and duplicated logic

## Verification

- `pnpm --filter @webstudio-is/asset-uploader test` — 196 tests passed
- Asset uploader typecheck passed
- Targeted Builder Assets API and upload client tests — 37 tests passed
- Builder typecheck passed
- `pnpm lint`

Closes #6148
2026-08-14 23:32:07 +02:00
Oleg Isonen 0d5c2f6ee8 fix: Allow folder index page paths (#6147)
## Summary

Fix the dry-run/commit mismatch for folder index pages. Page mutations
could plan an empty local path inside a folder, but persistence rejected
the same page because the shared Pages schema treated every non-home
empty path as invalid.

## Technical choices

- Derive valid folder-index page IDs from non-root folder children in
the shared SDK schema.
- Continue rejecting empty paths for non-home pages at the root.
- Keep the existing home-page and root-folder invariants unchanged.
- Cover both an existing page update and the transaction shape used when
duplicating a folder index page.

The other reports from this batch were verified against current main and
closed against their existing fixes: browser process failures are
covered by #6135, and fragment conflict options are covered by #6122.

## Checklist

- [x] Reproduce the persistence failure
- [x] Add regressions that fail before the schema fix and pass after it
- [x] Allow empty local paths only for pages parented by non-root
folders
- [x] Verify update and duplicate persistence paths
- [x] Run the fixture sync/build pipeline and inspect generated output
- [x] Review documentation impact — no documentation change is needed
because this restores the existing folder-index behavior
- [x] Review the final diff for duplicated logic, edge cases, and
unrelated changes

## Verification

- `pnpm --filter @webstudio-is/sdk test` — 513 tests passed
- SDK typecheck passed
- `pnpm --filter @webstudio-is/project test` — 85 tests passed
- Project typecheck passed
- `pnpm --filter @webstudio-is/project-build test` — 2,094 tests passed
- Project-build typecheck passed
- `pnpm --filter ./packages/cli test` — 980 tests passed
- CLI typecheck passed
- `pnpm lint`
- `pnpm check:generated-api`
- `pnpm fixtures` — completed with no tracked fixture changes
- Real local Chrome and auto-browser screenshot reproductions passed,
including parallel captures

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

Closes #6142
2026-08-14 23:31:27 +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 523f555cba fix: Make Content Block template JSX insertable (#6128)
## Summary

Fix MCP component discovery so Content Block template JSX can be passed
directly to `insert-fragment`.

The catalog previously advertised `<$.block-template />`. The hyphen is
invalid in a JSX member name, and the fallback `$` namespace also did
not match the canonical `ws:block-template` component.

## Implementation

- Advertise Webstudio-owned components through the `ws` JSX binding.
- Expose `ws:block-template` as the parser-safe `<ws.blockTemplate />`.
- Resolve that alias back to the unchanged canonical component ID at
runtime.
- Keep catalog formatting and runtime resolution on one shared alias
contract.
- Add regression coverage for inserting a Content Block, its required
template part, nested elements, and authored text.

Existing stored component IDs and Builder behavior remain unchanged.

## Todo

- [x] Advertise parser-compatible Content Block template JSX
- [x] Resolve the JSX alias to `ws:block-template`
- [x] Verify nested authored Content Block insertion
- [x] Verify other `ws:*` discovery entries use the correct namespace
- [x] Review documentation impact — no user-facing documentation changes
are required
- [x] Run project-build tests, typecheck, lint, formatting,
generated-doc checks, and fixtures
- [ ] Run the complete agent evaluation suite after explicit approval

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,090 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm lint`
- `pnpm exec oxfmt --check
packages/project-build/src/runtime/jsx/bindings.ts
packages/project-build/src/mcp.ts
packages/project-build/src/runtime/components.test.ts
packages/project-build/src/mcp.test.ts`
- `pnpm check:generated-docs`
- `pnpm fixtures` — completed with no fixture diff

Evaluations were not run because repository policy requires separate
explicit approval.

Closes #6127
2026-08-13 19:45:41 +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 a22b23936d docs: Explain Content Engine limits (#6123)
## Summary

- explain which kinds of blogs, catalogues, directories, and file-based
content fit the Content Engine
- identify projects better served by an external CMS, commerce backend,
or media service
- translate the current query, database, and content-loading limits into
practical guidance
- link readers to the complete Content Engine limits reference

## Why

The existing guide explains how to build a Markdown blog but does not
help readers decide whether the Content Engine is appropriate for their
content volume, media needs, or editorial workflow. This addition sets
expectations before readers commit to an architecture.

## Todo

- [x] Document suitable Content Engine use cases.
- [x] Document cases that should use an external system.
- [x] Confirm every stated limit against the current implementation and
generated reference.
- [x] Review documentation impact: the authoritative Content Engine
guide is updated; navigation is unchanged because this is an existing
page.
- [x] Verify the reference link and anchor.

## Verification

- `pnpm docs:content-engine:test` — passed
- `pnpm docs:content-engine:check` — generated files are clean
- verified `content-engine-reference.md#query-limits` exists
- `git diff --check` — passed

The repository pre-commit hook was bypassed after its `oxfmt` task
rejected the docs-only target because `docs/` is excluded from that
formatter. The hook restored the file unchanged; the
documentation-specific checks above passed before committing.
2026-08-13 11:23:12 +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 463a6b4fcc fix: Bind dynamic page metadata expressions (#6110)
## Summary

Bind readable page metadata expressions to stable scoped data-source
identifiers before persisting page mutations.

Dynamic page titles, descriptions, and other expression-capable metadata
can now reference:

- `system.params` and the rest of the built-in system context
- data variables and Resources available from the page root
- the same scoped identifiers used by instance and prop bindings

## Root cause

The page mutation path normalized and validated readable expressions
such as `system.params.slug` and `postForSlug.data`, but stored those
readable names unchanged. The SDK generator resolves persisted encoded
identifiers, so it replaced the unbound names with `undefined` in
`getPageMeta`.

Page create, update, and settings operations also loaded only the
`pages` namespace. Binding page-scoped expressions requires `instances`
and `dataSources` to resolve the page root's available variables.

## Implementation

- Bind every expression-capable page metadata field against the page
root before creating page patches.
- Load `pages`, `instances`, and `dataSources` for page
create/update/settings mutations.
- Regenerate the runtime operation contract.
- Cover both route parameters and a page-scoped Resource in the
regression test.
- Assert that page metadata mutations keep loading the complete
expression scope and that conflict retries refresh it.

## Impact

CLI/MCP-authored page metadata now compiles consistently with
Builder-authored and instance/prop bindings. Existing correctly encoded
expressions remain unchanged.

No database migration or compatibility fallback is required.

## Todo

- [x] Reproduce readable page metadata identifiers being persisted
unbound
- [x] Bind Title and expression-capable metadata fields to page scope
- [x] Load required expression namespaces for page mutations
- [x] Add regression coverage for `system.params` and a page-scoped
Resource
- [x] Regenerate runtime operation contracts
- [x] Run project-build tests, typecheck, lint, formatting, and diff
checks
- [x] Review documentation impact — no update required; existing
expression docs already describe the intended behavior
- [x] Review fixture impact — no fixture inputs or generated fixture
bundles are affected
- [x] Run agent evaluations — complete suite run twice; unrelated
agent/environment failures are documented below

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,083 tests passed
- `pnpm --filter @webstudio-is/sdk exec vitest run
src/page-meta-generator.test.ts` — 11 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck` — passed
- Focused `oxlint --deny-warnings` — passed
- `oxfmt` and `git diff --check` — passed
- Both new regression tests were confirmed to fail without their
corresponding behavior and pass with the fix
- `pnpm evaluations` — complete suite run twice, including once at PR
head `58641e7fd`. Metadata-relevant `expressions` and
`bindingVerification` checks passed. The suite did not pass overall
because of unrelated nondeterministic agent/environment failures:
invalid generated tool input and retry, a duplicate-page conflict,
token-budget variance, and `verify-page-responsive` returning `ENOENT`.
No evaluation-driven code changes were made.

Closes #6109
2026-08-12 19:22:32 +02:00
Oleg Isonen cb51d477e1 fix: Refresh resource diagnostics metadata (#6113)
## Summary

Follow up on #6095 so explicit Diagnostics refreshes update both
diagnostic and performance metadata, stale refreshes are cancelled
during invalidation, and Assets batch measurements are labeled
consistently in the Builder.

Ref #6092

## Technical choices

- Cache response diagnostics and performance metadata through one shared
path.
- Attach an AbortController to each explicit diagnostics request and
reject stale results after invalidation.
- Accept additive performance fields from newer servers.
- Clarify that Assets timings and counters cover the combined batch.
- Reuse the measured view-authorization path across repository entry
points.

## Implementation checklist

- [x] Refresh performance metadata with explicit diagnostics.
- [x] Cancel and ignore stale diagnostics requests.
- [x] Keep performance parsing forward-compatible with additive fields.
- [x] Clarify Assets batch labels and descriptions.
- [x] Add regression coverage for refresh, invalidation, and parsing
behavior.
- [x] Review documentation impact — authoritative `docs/` does not cover
this internal Diagnostics tooling, so no update is required.
- [ ] Run affected Builder and asset-uploader typechecks.
- [ ] Confirm GitHub checks pass.

## Verification

- Builder diagnostics/resource tests: 27 passed.
- Asset repository tests: 39 passed.
- `git diff --check origin/main...HEAD` — passed.
2026-08-12 11:46:58 +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 b113399021 fix: Restore Vercel sanitizer compatibility (#6103)
## Summary

Pin `sanitize-html` to 2.17.5 so deployed server bundles continue using
the CommonJS-compatible `htmlparser2@10`.

## Root cause

`sanitize-html@2.17.6` is still a CommonJS package but upgraded its
parser dependency to ESM-only `htmlparser2@12`. Vercel's Node function
loader rejects the resulting `require("htmlparser2")` call with
`ERR_REQUIRE_ESM`.

## Technical choice

Downgrade one patch release instead of patching third-party code or
adding a repository-wide parser override. This keeps the compatibility
constraint local to the package that owns `sanitize-html` and preserves
the existing sanitization API.

There are no migrations or public API changes.

## Todo

- [x] Pin `sanitize-html` to 2.17.5
- [x] Regenerate the pnpm lockfile
- [x] Verify CommonJS loading and sanitization
- [x] Run the affected package tests
- [x] Run the affected package typecheck
- [x] Check formatting
- [x] Run a Vercel standalone build and production-start smoke test
- [x] Review documentation impact — no user-facing documentation changes
are required
- [x] Review fixture impact — no fixture inputs or outputs are affected

## Verification

- CommonJS load and sanitization smoke test
- `pnpm test` in `packages/sdk-components-react`: 126 passed, 1 skipped
- `pnpm typecheck` in `packages/sdk-components-react`
- `pnpm exec oxfmt --check packages/sdk-components-react/package.json
pnpm-lock.yaml`
- Vercel CLI 58.9.3 standalone build with a forced `MarkdownEmbed`
server-render path
- Production SSR startup and `GET /`: HTTP 200 with the sanitized
Markdown heading rendered

## Known limitations

This intentionally stays on 2.17.5 until `sanitize-html` and the
deployment bundler have a compatible CommonJS/ESM integration.
2026-08-11 22:44:29 +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 11d76eb43d perf: Diagnose and reduce Assets resource latency (#6095)
## Summary

Adds Builder-only resource performance metrics to grouped Diagnostics
tables, removes duplicate object-store reads from the Assets query path,
scopes compiled-artifact reuse to one repository request, and compiles
unpinned Builder previews from their concrete query values instead of
hydrating the complete saved dynamic deployment plan. Nothing is
precomputed or retained in a warm serverless process after the request
finishes.

Ref #6092

## Technical choices

- Keep `__performance__` as internal response metadata and strip it
before resource values enter expression-visible caches.
- Record Builder round-trip, server duration, response size, Assets
phases, document work, completed storage reads, and cumulative
compiler/document-graph storage-read durations.
- Present Diagnostics as collapsible sections ordered Database and
sizes, Timing, Query work, then detailed database/result inspectors.
Open only the first available section by default. Every metric keeps an
explanatory tooltip, with info icons revealed only when the label cell
is hovered or keyboard-focused.
- Add a copy action to every section header. It copies that section's
raw structured data as formatted JSON without opening a collapsed
section.
- Keep Index preparation as an inclusive parent duration and explain
that relationship in Diagnostics.
- Compile ordinary unpinned Builder preview requests from a union of
their concrete query values. A detail route therefore hydrates only
documents that can satisfy that request.
- Continue using the saved artifact-producing plan for revision-pinned
requests.
- Keep publication and prepublication preparation unchanged.
- Load only `props`, `dataSources`, and `resources` when constructing
the single-query Builder preview plan, matching the already-narrow batch
path instead of downloading and parsing the complete dev Build row.
- Build the deployment-wide saved plan only when detailed Diagnostics
requests its Published database statistics. Report that work as the
separate inclusive `Published diagnostics` timing and exclude its child
phases and fetch counters from the normal query measurements.
- Reuse successfully read content bytes across compiler preparation,
document graph construction, and runtime resolution within one query or
batch. Graph-fetched bytes are size-validated before entering the
revision-keyed request map, and graph additions stop when the request
cache reaches the 2 MiB hydrated-content limit.
- Before repairing canonical metadata, load candidate metadata once and
prove its revision, base document, and prepared metadata tier match the
open source snapshot. Skip synchronization when it is already current;
retain recovery and repair for stale, missing, insufficient-tier, or
inconsistent rows.
- Key request-local bytes by content reference and revision so
source-change retries cannot reuse stale data.
- Instantiate compiled-artifact reuse per `PostgresAssetRepository`;
repeated preparations inside one request can reuse or coalesce work, but
later requests and other serverless instances cannot.
- Remove the module-level 32-entry compiled-artifact cache introduced in
#5946.
- Introduce no precomputed, persisted, process-local, or cross-request
content cache.

## Findings and measurements

The supplied production post resource was bimodal before removing the
process cache:

- Warm process: approximately 1.0–1.5 seconds.
- Cold process: approximately 5.7–6.7 seconds.
- Ordinary response: approximately 4.6 kB.

The earlier development capture was a process-cache hit: 542.6 ms server
time and 932.5 ms Builder round trip. A later hit measured 491.5 ms
server time and 739.1 ms round trip. Those results demonstrated the old
module cache's nondeterministic serverless behavior rather than
cold-path performance.

After request-scoped reuse replaced the process cache, the supplied cold
capture measured 3,848.5 ms server time. Index preparation was 3,259.7
ms. The saved dynamic plan caused 25 compiler content fetches and 301 kB
of compiler input even though the concrete post query resolved only two
documents. The published database contained 28 documents and used 45.4
kB bounded / 512 kB unbounded.

Tracing compilation also exposed duplicate request-time I/O: selected
Markdown was fetched for compiler input, document graph construction,
and runtime document resolution. Request-local byte reuse changes that
representative path from three object-store reads to one.

The remaining dominant issue was therefore query breadth, not response
serialization or runtime result resolution. The Builder was compiling
the deployment-wide dynamic plan to answer a concrete post request. The
new preview path compiles the concrete values; regression coverage
demonstrates that a saved two-post dynamic plan reads only `post-a` when
previewing `post-a`.

The next cold capture confirmed that change: Builder round trip fell
from 4,391.4 ms to 2,245.2 ms, server duration from 3,848.5 ms to
1,601.8 ms, and index preparation from 3,259.7 ms to 1,044.9 ms.
Compiler content fetches fell from 25 to 1 and document-graph content
fetches from 3 to 1. Canonical metadata remained 239.2 ms because an
unchanged request still performed a repair synchronization round and
then loaded the same stored metadata again. The new fast path reduces
that normal case to one recovery-safe candidate metadata read.

A subsequent capture confirmed the canonical metadata fast path: server
duration fell from 1,601.8 ms to 1,401.0 ms, index preparation from
1,044.9 ms to 684.8 ms, and canonical metadata from 239.2 ms to 74.7 ms.
The post query remained narrow at one compiler fetch and one
document-graph fetch. Builder round trip rose from 2,245.2 ms to 2,411.1
ms while the server improved, so that difference is outside the measured
server work and is consistent with
browser/network/development-environment variance.

At that point `buildPlan` was the largest remaining isolated phase at
242.8 ms. Single-query preview was still loading every dev Build column
and parsing pages, styles, instances, deployment data, and other fields
that are not inputs to the Assets compilation plan. The narrow loader
now requests and parses only the three content-engine fields used by
plan construction.

The next capture confirmed that optimization: build plan fell from 242.8
ms to 99.0 ms, server duration from 1,401.0 ms to 1,056.8 ms, and the
concrete post query remained at one compiler fetch and one
document-graph fetch. Index preparation stayed effectively flat at 679.7
ms, making compiler entries (142.3 ms), source snapshot (119.2 ms),
source validation (119.1 ms), and document graph (114.0 ms) the next
candidates. The new child timings separate cumulative storage waiting
from the remaining compiler/document-graph work before choosing another
behavioral optimization.

The storage timing capture then showed that I/O dominated the remaining
compiler and graph phases: compiler storage was 114.6 ms of 125.9 ms,
while document-graph storage was 114.0 ms of 147.6 ms. The graph path
consulted compiler-cached bytes but did not retain newly fetched graph
documents, so runtime reference resolution fetched the same two-document
chain again. Retaining validated graph bytes in the same request reduces
the regression fixture from four physical object-store reads to two
without changing its resolved result.

Final three-run measurements after same-request graph-byte reuse were
stable: median Builder round trip was 926.2 ms, median server duration
was 681.9 ms, median index preparation was 407.9 ms, and median document
resolution was 3.7 ms. Compared with the uncached 3,848.5 ms server /
3,259.7 ms index baseline, that is approximately 82% less server time
and 87% less index-preparation time for this post query.

A final DRY/correctness review centralized request-local byte-cache
keying and its 2 MiB accounting, removed repeated Diagnostics size rows
and SDK metadata assembly, typed phase keys, and corrected response-size
measurement so internal performance metadata is excluded as the UI
promises.

The module cache was also only bounded for completed entries. Many
simultaneous unique-project compilations in one process could exceed 32
pending entries until they settled. Scoping reuse to one repository
request removes cross-user retention and tenant cache competition while
preserving duplicate-work elimination inside the request.

## Todo

- [x] Add generic resource timing and response-size metrics.
- [x] Add Assets phase, document graph, source validation, storage-read
count/byte metrics, and compiler/document-graph storage-read durations.
- [x] Render ordered collapsible Diagnostics sections with only the
first open by default, per-metric explanations, interaction-only info
icons, and per-section JSON copy actions.
- [x] Reuse compiler and document-graph content bytes through runtime
resolution within the current request, without precomputation.
- [x] Remove cross-request process caching and retain request-scoped
compilation reuse.
- [x] Compile unpinned previews from concrete query values.
- [x] Separate deployment-wide diagnostic preparation from normal query
phase counters.
- [x] Skip redundant canonical metadata synchronization when stored
candidates already match the source snapshot and requested tier.
- [x] Narrow single-query Build reads to the three fields required by
Assets plan construction.
- [x] Expose cumulative compiler and document-graph storage-read timing
as explained child rows.
- [x] Add regression coverage, including red/green verification.
- [x] Review the final diff for correctness, duplication, and
unnecessary complexity.
- [x] Verify concrete-query compilation against the supplied post
resource: compiler fetches fell from 25 to 1.
- [x] Remeasure the canonical metadata fast path: canonical metadata
fell from 239.2 ms to 74.7 ms and index preparation from 1,044.9 ms to
684.8 ms.
- [x] Review documentation impact: no authoritative `docs/` page covers
this internal Diagnostics tooling, so no documentation update is
required.
- [x] Run focused tests, affected package typechecks, lint, formatting,
and the fixture pipeline.

## Verification

- Content engine tests: 23 passed.
- Asset uploader tests: 195 passed, including repository, canonical
synchronization, request-cache, and storage-timing coverage.
- Builder focused Diagnostics tests: 10 passed in the latest
storage-timing pass; 15 passed in the preceding final pass and 35 across
the earlier instrumentation scope. The collapsible view test verifies
section order, default state, and deferred content interaction.
- Project tests: 84 passed, including 5 focused preview tests. The
focused regression failed against the full-Build loader and passed after
switching the single-query path to the narrow content-engine loader.
- SDK resource loader tests: 24 passed.
- Current canonical metadata regressions failed under the old
always-synchronize expectations and pass with one metadata load and no
synchronization; stale metadata still exercises and passes the repair
fallback.
- The Timing clipboard test failed with an empty payload and passed with
the complete raw duration/phase JSON; copying a collapsed section is
also verified not to open it.
- The collapsible default-state test failed when Timing was temporarily
opened alongside Database and sizes, then passed after restoring the
first-section-only behavior.
- The concrete-preview regression failed before the optimization by
reading both `post-a` and `post-b`, then passed by reading only
`post-a`.
- A repository-reuse regression failed without the request cache and
passed when reuse became the repository default.
- The content-read regression failed before the original compiler reuse
optimization with three storage calls and passed afterward with one.
- The two-document graph regression failed with four physical storage
calls and passed with two after graph reads populated the request-local
runtime cache.
- The response-size regression failed at 127 bytes when internal
performance metadata was included and passed at the actual 66-byte
payload after separating that metadata.
- Builder, content engine, asset uploader, project, and SDK typechecks
passed.
- Oxfmt, oxlint, and `git diff --check` passed.
- The full fixture link/sync/build pipeline passed with no tracked
fixture changes after each runtime change.

## Known limitations

- Detailed Diagnostics intentionally prepares both the concrete query
database and the deployment-wide Published database. `Published
diagnostics` reports the latter separately; normal resource loads do not
perform it.
- Builder round-trip and server duration cover the complete resource
batch and therefore include Published diagnostics when the detailed view
requests it.
- Phase durations are aggregated when a batch contains multiple Assets
queries.
- Compiler and document-graph storage-read timings sum completed fetch
durations. They exclude request-local byte-cache hits and can exceed
wall-clock parent phases when reads run concurrently.
- Content-read counters report completed normal-query reads;
deployment-wide diagnostic reads are excluded, and failed storage reads
are represented by the enclosing phase timing and error.
- Request-local byte reuse retains selected content only until the
current query or batch finishes. Document-graph additions are capped by
the existing 2 MiB hydrated-content limit; content beyond the cap is
still streamed and can be fetched again at runtime.
- Source snapshot and final source validation remain separate reads. The
metadata fast path removes redundant repair work without weakening that
concurrent-change check.

## Measurement gate

PR deployments use the development backend. The supplied production
project returns 500 on both the branch deployment and
`main.development.webstudio.is`, while the production host returns 200.
Final phase timings for the exact project require its corresponding
development project/session or an explicitly authorized
production-backed deployment. An unrelated fixture will not be used to
claim the production improvement.
2026-08-11 12:31:23 +02:00
Oleg Isonen 43a9f824ad fix: Resolve published asset paths in structured content (#6096)
## Summary

Resolve Markdown frontmatter and JSON values written as published paths
such as `./assets/hero.png` or `/assets/hero.png`, even when the content
document and asset live in sibling Asset folders.

This staging PR replaces #6094 and supersedes #6016. #6016's raw-format
changes were unrelated to the reported asset-path problem.

## Technical approach

- Keep format-neutral path indexing and resolution in
`asset-path-resolution.ts`.
- Keep `markdown-assets.ts` limited to Markdown destination parsing.
- Normalize both Markdown frontmatter and JSON objects through the
shared structured-value reference pipeline.
- Keep canonical source-relative resolution as the first choice.
- Recognize only the published `assets/<file-name>` path shape as a
fallback.
- Match the final canonical asset file name only when it uniquely
identifies one project asset.
- Leave external URLs, missing assets, ambiguous names, and unrelated
root-relative paths unchanged.
- Resolve recorded asset references to runtime URLs when queries render
their results.

## Compatibility

This extends resolution for imported/published structured content
without changing ordinary source-relative paths or direct asset ID
references. Duplicate final file names intentionally remain unresolved
to avoid choosing the wrong asset.

## Checklist

- [x] Add regression coverage for relative and root-relative published
asset paths.
- [x] Cover discovery and runtime structured-value resolution.
- [x] Add explicit JSON document coverage with the asset in a sibling
folder.
- [x] Verify ambiguous asset names remain unresolved.
- [x] Separate format-neutral resolution from Markdown parsing.
- [x] Run the full content-engine test suite.
- [x] Run content-engine typecheck and targeted lint/format checks.
- [x] Rebuild content runtime and repository fixtures.
- [x] Review documentation impact and update the Assets guide.
- [ ] Manually verify the published site from `md-assets.staging`.
- [ ] Confirm CI checks pass.

## Verification

- Regression tests were run with the published-path fallback disabled
and failed for both generic structured values and the explicit JSON
integration case.
- `pnpm --filter @webstudio-is/content-engine test` — 294 tests passed.
- `pnpm --filter @webstudio-is/content-engine typecheck` — passed.
- `pnpm exec oxlint --deny-warnings ...` — passed.
- `pnpm exec oxfmt --check ...` — passed.
- `pnpm --filter webstudio build:content-runtime` — passed.
- `pnpm -r fixtures:build` — passed; no generated fixture diff.
- `git diff --check` — passed.
- Agent evaluations were not run because they require separate explicit
approval.

Closes #6093
2026-08-10 21:48:32 +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 db3b11970f fix: Optimize images in Markdown Embed (#6089)
## Summary

Markdown Embed now renders image tags with the same optimized attributes
and canvas behavior as the SDK Image component.

The implementation shares the complete attribute-resolution path without
rendering React inside the Markdown pipeline:

- `@webstudio-is/image` exposes one framework-independent
`getImageAttributes` function for optimization, placeholder fallback,
loading, decoding, dimensions, alternative text, and responsive sources.
- React-only element props such as `className`, `style`, handlers,
context, and refs stay in React callers.
- The SDK Image component and Markdown renderer both use
`getSdkImageProps` for canvas-specific loading, decoding, asset
identity, and incomplete-upload handling.
- Markdown retains only the necessary HTML attribute-name conversion,
sanitization, and Safari-safe `sizes` / `srcset` / `src` ordering.
- The redundant low-level React Image primitive was removed. Its callers
now render native `img` elements, and the image package no longer has
React runtime or peer dependencies.

## Compatibility and tradeoffs

- The SDK Image component remains the public React component because it
owns React context and ref forwarding.
- The removed low-level `@webstudio-is/image` Image export is replaced
by the pure attribute function for internal callers.
- No data migration or schema change is required.
- Loader output is sanitized after transformation, while unsafe input
URLs are sanitized before reaching the loader.

## Todo

- [x] Share image attribute resolution between native, SDK, and Markdown
images.
- [x] Preserve SDK canvas behavior for Markdown images.
- [x] Preserve safe URL sanitization and Safari-safe responsive
attribute ordering.
- [x] Remove the redundant low-level React image primitive and migrate
its callers.
- [x] Remove the overlapping `getImageProps` abstraction.
- [x] Add regression coverage comparing Markdown, SDK Image, and native
attribute output.
- [x] Review documentation impact and update the Markdown Embed
documentation.
- [x] Run targeted tests, typechecks, builds, lint, formatting,
package-boundary checks, and fixture generation.

## Verification

- `pnpm --filter @webstudio-is/image test`
- `pnpm --filter @webstudio-is/image typecheck`
- `pnpm --filter @webstudio-is/image build`
- `pnpm --filter @webstudio-is/image dts`
- `pnpm --filter @webstudio-is/sdk-components-react test`
- `pnpm --filter @webstudio-is/sdk-components-react typecheck`
- `pnpm --filter @webstudio-is/sdk-components-react build`
- `pnpm --filter @webstudio-is/sdk-components-react dts`
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm --filter @webstudio-is/design-system typecheck`
- Targeted Oxlint and Oxfmt checks
- `pnpm check:package-boundaries`
- `pnpm fixtures` (no fixture diff)

Ref #6014
2026-08-10 18:21:45 +02:00
Oleg Isonen 50958a68b3 ci: Restore safe-to-deploy workflows (#6054)
## Summary

Restore CI, visual testing, and Vercel preview workflows for reviewed
fork pull requests after `actions/checkout` began refusing fork code in
`pull_request_target` jobs by default.

The affected workflows now also use Node 24-compatible action releases,
and the shared Builder E2E artifact is retained long enough for delayed
failed-job reruns.

Closes #6053

## Root cause

Applying `safe-to-deploy` still triggered the intended workflows, but
each job stopped at checkout with:

> Refusing to check out fork pull request code from a
`pull_request_target` workflow.

The label is the repository's explicit human-review gate for trusted
fork code, but the checkout steps did not pass the action's new explicit
opt-in.

A later failed-job rerun also could not download `builder-e2e-build`
because its one-day retention period had elapsed. The affected
JavaScript actions additionally targeted the deprecated Node 20 action
runtime.

## Implementation

- Opt into fork checkout only when the event is `pull_request_target`
and the applied label is exactly `safe-to-deploy`.
- Apply the opt-in to Main, Chromatic, Visual Regression Tests, and
Vercel staging checkout steps.
- Gate `check-size` with the same push, regular PR, or reviewed-fork
condition as the other Main workflow jobs.
- Gate `deployment-host` so synchronize events only remove the approval
label and do not check out fork code.
- Upgrade the affected Checkout, Setup Node, pnpm setup, Cache,
artifact, GitHub Script, and Chromatic actions to Node 24-compatible
majors.
- Retain `builder-e2e-build` for seven days so delayed failed-job reruns
can download it.
- Preserve existing push and normal `pull_request` paths.

## Security tradeoff

`pull_request_target` jobs have base-repository credentials and secrets.
This change deliberately permits fork checkout only after a maintainer
applies `safe-to-deploy`; no other label or synchronize event receives
the opt-in.

## Todo

- [x] Reproduce and inspect the checkout failure on fork PR #6041.
- [x] Add explicit checkout opt-in behind the trusted label.
- [x] Gate previously unconditional jobs before enabling fork checkout.
- [x] Upgrade affected JavaScript actions to Node 24-compatible
releases.
- [x] Keep Builder E2E prerequisites available for delayed reruns.
- [x] Review documentation impact; no product documentation is affected.
- [x] Verify fixture inputs and outputs are unaffected.
- [ ] Verify the labeled fork path after this workflow version reaches
`main`.

## Verification

- `git diff --check`
- Parsed all changed workflow and composite-action YAML files with
Ruby's YAML parser.
- Verified each upgraded action manifest declares the Node 24 runtime.
- Verified `actions/checkout@v5` retains the `allow-unsafe-pr-checkout`
input.
- Audited every `actions/checkout` step in the affected
`pull_request_target` workflows for the exact label guard.
- PR checks passed for Main, Chromatic, Vercel staging, submodules, and
PR title validation; Lost Pixel remains excluded from this fix.

The live labeled-fork execution cannot be proven from this PR itself
because `pull_request_target` loads workflow definitions from the base
branch. It should be verified with a fork PR after this change reaches
`main`.
2026-08-10 16:15:19 +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 8e4ee80bad fix: Report the constraining HTML ancestor (#6083)
## Summary

Fix content-model warnings so they identify the ancestor that actually
introduces the violated HTML constraint, rather than always naming the
offending element’s immediate parent.

For Link > Div > controlled Video, the warning now identifies `<a>` as
the constraint source. The finding location remains the Video instance,
so **Show element** still selects the element users need to inspect.

## Root cause

The validator correctly propagated HTML constraints through nested and
transparent containers, but discarded where each effective constraint
originated. Warning construction then looked only at the immediate
parent, even when that parent allowed the child and a higher ancestor
caused the violation.

## Implementation

- Reconstruct the effective constraint state along the offending
element’s ancestor path when a validation failure needs reporting.
- Track the last valid-to-invalid transition to identify the ancestor
that introduced the effective violation.
- Cover interactive constraints through a Div and category constraints
through a tagless Slot.
- Update fragment-validation expectations that intentionally assert the
user-visible error contract.

## Todo

- [x] Reproduce the misleading Video/Div publish warning.
- [x] Identify the ancestor that introduces an inherited HTML
constraint.
- [x] Preserve the offending child as the finding location for **Show
element**.
- [x] Cover ordinary and tagless/transparent intermediate containers.
- [x] Run package tests, typecheck, lint, formatting, and diff checks.
- [x] Review documentation impact; no documentation update is required
because this corrects diagnostic wording.
- [x] Review fixture impact; no fixture inputs or generated fixture
outputs are affected.

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 82 files, 2,099
tests passed.
- `pnpm --filter @webstudio-is/project-build typecheck` — passed.
- Focused `oxlint` and `oxfmt` checks — passed.
- `git diff --check` — passed.

Closes #6082
2026-08-10 10:59:04 +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 41c8283a70 fix: Allow removal of invalid audited selectors (#6074)
## Summary

Allow all targeted style-deletion paths to remove an invalid state
selector that already exists, including Builder UI deletion of local and
token declarations and CLI/MCP design-token remediation. Creation and
update validation remain strict.

## Root cause

The style audit reads legacy declarations directly and can report
selectors that the current selector validator rejects. Deletion reused
the creation/update validator, so the system could identify an invalid
stored declaration but could not represent its coordinates for targeted
removal. The first fix addressed only design-token deletion, leaving
Builder UI and the generic style APIs inconsistent.

## Implementation

- [x] Give style deletions a shared deletion-only state-selector
contract.
- [x] Accept an invalid selector only when the exact target, breakpoint,
property, and state already exist.
- [x] Reject arbitrary invalid selectors that do not identify an
existing declaration.
- [x] Reuse one matcher for local, selected/token, and design-token
deletions.
- [x] Keep creation and update selector validation strict.
- [x] Add Builder UI regression coverage for token and local declaration
deletion.
- [x] Add an audit regression that deletes the reported design-token
selector, applies the patch, and confirms the warning is cleared.
- [x] Regenerate the public runtime operation contract.
- [x] Complete the DRY review: deletion matching, validation, key
deduplication, and stored-style scanning are centralized.
- [x] Review documentation impact: no authored documentation changes are
required; generated operation contracts describe the behavior.
- [x] Review fixture impact: no fixture inputs or generated fixture
bundles are affected.
- [x] Run relevant tests, typechecks, lint, formatting,
package-boundary, generated-API, and generated-documentation checks.
- [ ] Agent evaluations — not run because repository policy requires
separate explicit approval.

## Verification

- The two Builder UI regression tests failed with `Operation input is
invalid` before the shared fix and passed afterward.
- Project-build suite: 82 files, 2,097 tests passed.
- CLI suite: 60 files, 968 tests passed.
- Builder style-data target: 7 tests passed.
- Audit and styles targets: 195 tests passed.
- Project-build, Builder, and CLI typechecks passed.
- Full repository lint passed.
- Package-boundary, generated-API, and generated-documentation checks
passed.
- Formatting and `git diff --check` passed.

Closes #6062
2026-08-09 01:46:42 +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
Kathir EswaranandOleg Isonen ce84ddcf12 fix: Remove hardcoded template allowlist and require authorization tokens (#6041)
## Description

Removes the hardcoded template-project allowlist from
`checkProjectPermit`. Marketplace projects now use their existing viewer
authorization tokens, matching how the marketplace passes `authToken`
through builder URLs and the builder loader.

Token authorization is also scoped to the requested project, so a valid
token for one project cannot grant access to another project.

## Changes

- Removed the 15-project template allowlist and its unconditional view
grant.
- Scoped `AuthorizationToken` permit queries by `projectId`.
- Added regression coverage for former template IDs, valid viewer
tokens, missing tokens, and cross-project token use.
- Removed the proposed token backfill and unrelated Prisma schema drift
change; existing marketplace tokens remain the source of access.

## Verification

- [x] Demonstrated the cross-project regression test fails before the
project constraint.
- [x] `pnpm --filter @webstudio-is/trpc-interface test --
src/authorize/project.server.test.ts`
- [x] `pnpm --filter @webstudio-is/trpc-interface typecheck`
- [x] Reviewed documentation impact; no documentation changes are
required.
- [x] Verified fixture inputs and outputs are not affected.

## Before merging

- [x] Made a self-review.
- [x] Added regression tests.
- [x] Confirmed no environment variables are added.
- [ ] Confirm GitHub checks pass.

---------

Co-authored-by: Oleg Isonen <oleg008@gmail.com>
2026-08-07 19:57:24 +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 d8491ea736 fix: Make bound fields read-only (#6028)
Closes #6027

## Summary

Bound values currently remain editable when the expression directly
references a mutable variable. Editing the field writes a temporary
runtime override while leaving the binding in place, which makes the
source of truth unclear and behaves differently across Builder fields.

This change makes every expression-bound field read-only. To enter a
literal value, remove the binding first.

## Implementation

- Simplify binding state to two states: unbound/editable and
bound/read-only.
- Remove temporary data-variable override writes and the
overwritten/reset binding UI.
- Remove `allowBindingOverwrite` and bound-value parsing APIs and their
call-site exceptions.
- Guard pending draft saves so a draft started before binding cannot
replace the new binding.
- Add query-builder regression coverage for string and numeric filters
bound to mutable variables.

## Checklist

- [x] Add regression tests and confirm they fail with the previous
behavior.
- [x] Make all expression-bound controls read-only.
- [x] Remove temporary override state and UI.
- [x] Verify literal controls remain editable.
- [x] Run the full Builder unit test suite.
- [x] Run Builder typecheck and repository lint.
- [x] Review documentation impact: no documentation change is needed;
this makes the existing remove-binding-before-edit interaction
consistent.
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected.

## Verification

- `pnpm --filter @webstudio-is/builder test` — 2,509 tests passed across
217 files.
- `pnpm --filter @webstudio-is/builder typecheck` — passed.
- `pnpm lint` — passed.
- Agent evaluations were not run because they require separate explicit
approval and this change does not modify agent-facing behavior.
2026-08-07 12:34:11 +02:00