Commit Graph
6637 Commits
Author SHA1 Message Date
Oleg Isonen fc6b5f2fde fix: Retry bulk style deletions after conflicts (#6255)
Closes #6254

## Summary

- Mark `styles.deleteDeclarations` as safe to retry after a confirmed
version conflict.
- Recompute the deletion against refreshed style namespaces before the
retry.
- Add a regression covering 185 CSS custom-property declarations and the
complete conflict/refresh/retry path.
- Regenerate the public runtime operation contract.

## Root cause and behavior

`delete-styles` already accepts and plans a 185-declaration payload. The
failure occurs when the remote build changes between the session
snapshot and the commit: the session refreshes, but the operation
contract currently has `retryOnConflict: false`, so the atomic deletion
returns uncommitted and the project version does not advance.

Style declaration deletion is retry-safe because the operation is
recomputed from the refreshed snapshot and removes only exact
declaration keys that still exist.

The released CLI already writes structured success and failure JSON to
stdout; stderr contains lifecycle diagnostics. No stderr parsing change
is needed in Webstudio. The issue's stderr acceptance criterion was
LLM-authored without captured failure diagnostics in CLI 0.293.0.

## Tradeoffs and compatibility

- Retries only confirmed version conflicts.
- Ambiguous commit results are still not replayed.
- No schema, migration, or user-facing API shape changes.

## Checklist

- [x] Reproduce the 185-declaration CSS-variable deletion through the
CLI/MCP dry-run path.
- [x] Reproduce the unchanged-version failure with a conflicting commit.
- [x] Verify the regression fails before the contract change and passes
afterward.
- [x] Enable conflict retry for `styles.deleteDeclarations`.
- [x] Regenerate runtime operation contracts.
- [x] Review documentation impact; no documentation update is required
for this reliability fix.
- [x] Verify fixture impact; no fixture inputs or outputs are affected.

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,132 tests passed.
- `pnpm --filter @webstudio-is/project-build typecheck` — passed.
- Targeted oxlint on changed source/tests — passed.
- `pnpm check:generated-api` — passed; generated files are clean.
- Agent evaluations were not run because they require separate explicit
approval.
2026-08-27 23:50:15 +02:00
Oleg Isonen 981f45fd0d docs: Optimize documentation image sizes (#6253)
## Summary

Replace ten oversized PNG screenshots in the product documentation with
high-quality WebP assets and update their references.

The affected assets shrink from 27,813,051 bytes to 2,822,010 bytes (90%
smaller) while retaining their original pixel dimensions.

## Technical choices

- Encoded screenshots as WebP at quality 88 with sharp YUV conversion.
- Kept original dimensions to preserve UI text legibility and zoom
detail.
- Left animated GIFs unchanged: GIF-native optimization, palette
reduction, and resizing tests increased their file sizes, so changing
them would add cost or reduce clarity.
- Kept documentation content, navigation, and public page paths
unchanged.

## Checklist

1. [x] Audit raster assets in `docs/.gitbook/assets`.
2. [x] Optimize referenced PNG assets above 1 MB where conversion
materially reduces size.
3. [x] Update every affected documentation reference.
4. [x] Verify replacement dimensions and image decoding.
5. [x] Verify visual quality (SSIM 0.961–0.997 plus manual spot checks).
6. [x] Verify new references resolve and removed filenames are no longer
referenced.
7. [x] Review documentation impact; this PR changes documentation assets
and their direct references only.
8. [x] Verify no fixture inputs or generated fixture outputs are
affected.

## Verification

- `git diff --check`
- Reference checks for all ten added and removed filenames
- Pixel-dimension comparison for all ten replacements
- FFmpeg SSIM comparison for all ten replacements
- Manual visual review of representative full-resolution screenshots

The repository formatter excludes these documentation files, so it
reported no matching targets. Agent evaluations were not run because
this is a documentation-asset-only change.

Closes #6252
2026-08-27 17:39:10 +02:00
Oleg Isonen 471f504087 feat: Select unused assets before deleting (#6245)
Closes #6243

## Summary

- Add checked-by-default controls for every asset in the **Delete unused
assets** dialog.
- Add a footer **Select all** control that reflects the complete
selection and toggles all assets.
- Delete only selected assets that remain unused and disable deletion
when the selection is empty.
- Keep the Assets documentation aligned with the selective deletion
workflow.

## Implementation

The dialog owns a set of selected asset IDs initialized from the
unused-assets result. Individual checkboxes update that set, while
**Select all** replaces it with either every currently unused ID or an
empty set.

Asset usage inputs remain subscribed while the dialog is open. The
deletion payload is intersected with the current unused-assets list, and
this cleanup path does not force deletion. If an asset becomes
referenced before the mutation executes, the runtime rejects its
deletion instead of removing a now-used asset.

## Todo

- [x] Add per-asset selection controls.
- [x] Add and position the **Select all** footer control.
- [x] Limit deletion to selected asset IDs that remain unused.
- [x] Protect against assets becoming referenced while the dialog is
open.
- [x] Add regression tests for individual selection, select-all
behavior, empty-selection protection, selective deletion, and live usage
changes.
- [x] Review documentation impact and update the Assets guides.
- [x] Verify formatting, lint, types, and Asset Manager/assets tests.

## Verification

- `pnpm --filter @webstudio-is/builder exec vitest run
app/builder/shared/asset-manager app/builder/shared/assets` — 26 files,
221 tests passed.
- `pnpm --filter @webstudio-is/builder typecheck` — passed.
- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/shared/assets/delete-assets.ts
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.tsx
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.test.tsx`
— passed.
- `pnpm exec oxfmt --check
apps/builder/app/builder/shared/assets/delete-assets.ts
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.tsx
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.test.tsx`
— passed.
- Both regression tests were confirmed failing before their respective
implementations and passing afterward.

## Compatibility and follow-ups

No data migrations, public API changes, fixture changes, or known
follow-ups.
2026-08-26 12:14:06 +02:00
Oleg Isonen 9a4ee57929 fix: Create page templates without explicit titles (#6240)
## Summary

Allow `create-page-template` to omit `title` by deriving a fixed title
expression from the required template name. Explicit titles remain
unchanged.

Closes #6239

## Todo

- [x] Reproduce the name-only mutation failure against the persisted
page-template schema
- [x] Derive the omitted title from the template name
- [x] Add and verify a fail-before/pass-after regression test
- [x] Review fixture impact; no fixture inputs or generated outputs are
affected
- [x] Review documentation impact; the existing command schema and
example already document the optional title, so no documentation update
is needed

## Verification

- `pnpm --filter @webstudio-is/project-build test --
src/runtime/page-copy.test.tsx`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm exec oxfmt --check
packages/project-build/src/runtime/page-copy.ts
packages/project-build/src/runtime/page-copy.test.tsx`
- `pnpm exec oxlint --deny-warnings
packages/project-build/src/runtime/page-copy.ts
packages/project-build/src/runtime/page-copy.test.tsx`
- Full package test run: 2,127 passed; three unrelated existing
descendant-selector failures remain in `src/runtime/styles.test.ts`
- Agent evaluations were not run because they require separate approval
2026-08-26 11:01:29 +02:00
Oleg Isonen db2175f333 fix: transfer pasted assets across deployments (#6228)
Closes #6227

## What changed

Cross-deployment copy/paste now transfers referenced assets into the
destination project before inserting instances, pages, templates, or
folders.

- Add optional source deployment metadata to the existing v0.1 clipboard
formats.
- Keep old v0.1 payloads readable and keep new payloads readable by
older builders, which ignore the additive field.
- Reuse the existing asset upload-by-URL flow for images, fonts, videos,
and files.
- Preserve source video dimensions through the existing upload route.
- Replace source asset IDs and font-family references with destination
values before runtime insertion.
- Include and remap page social images and marketplace thumbnails.
- Reject current clipboard payloads whose page metadata references
omitted assets.
- Leave the public `pageTransfer.insert` runtime input unchanged.
- Abort insertion with a paste error when an asset cannot be imported.

No clipboard version increase, custom network fetcher, address
blocklist, rollback framework, or global paste wrapper is introduced. If
a multi-asset import partially uploads before a later upload fails, the
completed uploads may remain unused.

## Checklist

- [x] Rebased onto `main`
- [x] Preserve the existing v0.1 clipboard format
- [x] Transfer and remap instance assets
- [x] Transfer and remap page, template, and folder assets
- [x] Preserve video metadata
- [x] Keep runtime page-transfer API compatible
- [x] Add regression coverage
- [x] Review documentation impact and update page-copy documentation
- [x] Run Builder tests and typecheck
- [x] Run project-build tests and typecheck
- [x] Run lint, package-boundary, and generated-API checks

## Verification

- Builder full suite: 219 files, 2,552 tests passed
- Project-build full suite: 78 files, 2,129 tests passed
- Final affected suite: 84 tests passed
- New regressions were confirmed failing before the fixes and passing
afterward
- Builder and project-build typechecks passed
- `pnpm lint` passed
- `pnpm check:package-boundaries` passed
- `pnpm check:generated-api` passed
- Fixture inputs and generated fixture bundles are unaffected
2026-08-25 16:44:38 +02:00
Oleg Isonen 61df069016 fix: Report recovered project settings commits (#6236)
## Summary

A stale project-settings mutation could retry and commit successfully
while retaining the original `CONFLICT` error diagnostic. CLI and MCP
then reported failure even though the requested settings were stored and
the project version advanced.

Successful conflict retries now retain only recovery information. Failed
retries still preserve their errors.

## Implementation checklist

- [x] Reproduce the stale project-settings conflict before changing the
implementation.
- [x] Remove the obsolete error diagnostic only after a retry succeeds.
- [x] Cover `update-project-settings` commit status, resulting version,
and remote read-back.
- [x] Confirm the regression fails before the fix and passes after it.
- [x] Review documentation impact; no user documentation changes are
required.
- [x] Review generated contracts and fixtures; neither is affected.
- [ ] Confirm CI passes.

## Verification

- ProjectSession suite: 49 tests passed.
- Complete Project Build suite: 2,126 tests passed.
- Project Build typecheck passed.
- CLI ProjectSession adapter suite: 8 tests passed.
- Changed-file lint and formatting passed.
- Agent evaluations were not run because they require separate approval.

Closes #6235
2026-08-25 15:03:50 +02:00
Oleg Isonen 130d371759 perf: Skip resources in statically hidden subtrees (#6233)
## Summary

- Skip read-time resources scoped to an instance with literal
`show=false` or any descendant of that instance.
- Use the same static reachability rule in the Builder client preloader
and CLI-generated published resource map.
- Keep dynamic `show` expressions on the existing loading path because
their result may depend on resource data.

## Data path and performance

In the Builder, the client-side resource request set now excludes
statically hidden subtrees before requests enter the preloader.

For generated sites, the CLI still scans project instances and props
during build, but removes statically hidden subtrees from the page build
data. Their read resources therefore never enter the generated
server-side resource map and no corresponding resource network request
runs during page rendering.

This removes request and server/network cost for literal-`false`
subtrees. It does not remove build-time project scanning, and resources
under dynamic conditions still load.

## Compatibility

- No project schema or persisted data changes.
- No migrations.
- Action-bound resources retain their existing explicit-trigger
behavior.
- Dynamic `show` expressions retain current behavior.

## Checklist

- [x] Add shared static-subtree reachability logic.
- [x] Apply it to Builder preview resource preloading.
- [x] Apply it to generated/published resource collection.
- [x] Add Builder and generated-output regressions for direct,
descendant, visible, and dynamic cases.
- [x] Demonstrate the new regressions fail against the previous behavior
and pass with the implementation.
- [x] Review authoritative `docs/` content; no documentation update is
required because the UI and authoring workflow are unchanged.
- [x] Review fixture inputs and outputs; no fixture contains this
resource/show combination, so fixture regeneration is not required.

## Verification

- `pnpm --filter @webstudio-is/sdk test`
- `pnpm --filter @webstudio-is/react-sdk test`
- `pnpm --filter @webstudio-is/builder exec vitest run
app/shared/nano-states/props.test.tsx`
- `pnpm --filter webstudio build:content-runtime`
- `pnpm --filter webstudio exec vitest run src/prebuild.test.ts`
- Typechecks for SDK, React SDK, Builder, and CLI
- Targeted `oxlint --deny-warnings`
- `git diff --check`

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

Closes #6232
2026-08-24 18:45:46 +02:00
Oleg Isonen 8e3f8e3234 fix: Avoid Builder E2E registry throttling (#6231)
## Summary

- pull the pinned Supabase Postgres image from its official GHCR package
instead of anonymous Public ECR
- pin the multi-platform index digest shared by both registries

## Technical choice

The existing Public ECR and GHCR tags resolve to the same multi-platform
index digest, so changing registries preserves the amd64 and arm64 image
contents. GHCR public pulls require no long-lived CI credentials.

## Checklist

- [x] Verify Public ECR and GHCR resolve to the same image digest
- [x] Switch the Compose database image to the pinned GHCR digest
- [x] Validate shell syntax and rendered Compose configuration
- [x] Review documentation impact; no user-facing documentation is
affected
- [x] Review fixture impact; no fixture inputs or outputs are affected

## Verification

- `bash -n apps/builder/dev/backend.sh apps/builder/e2e/run-local.sh`
- `docker compose ... config --images`
- `docker buildx imagetools inspect` digest comparison for both
registries
- `git diff --check`

The full Builder E2E suite was not run locally because the change only
selects an identical image from a different registry.

Closes #6230
2026-08-24 16:07:21 +02:00
Oleg Isonen 9e504037fb fix: improve MCP reliability across project workflows (#6222)
## Summary

Fixes the reproducible MCP, preview, redirect, audit, mutation, and
asset-upload failures from the latest issue batch. Historical and
non-reproducible reports were investigated without speculative fixes.
Visual verification is now opt-in unless the user explicitly requests
it.

## Issue checklist

- [x] #6207 — report tested recovery when an explicitly selected browser
fails to start.
- [x] #6208 — accept generic and registered buttons as valid label
targets without requiring a separate form label.
- [x] #6209 — reject unknown style coordinates such as `breakpointId`
and document the supported `breakpoint` field.
- [x] #6210 — traced Node 18 access to CLI 0.293.0 predating the
launcher guard; closed as already fixed by #6201.
- [x] #6211 — verified the supported launcher blocks the reported Node
18 crypto path; closed as already fixed by #6201.
- [x] #6212 — give sequential responsive captures the sum of their
per-capture timeout budgets.
- [x] #6213 — keep preview install, build, and server subprocesses on
the parent CLI Node runtime.
- [x] #6214 — add safe confirmation and diagnostics for ambiguous
mutation responses; this does not claim to remove upstream latency.
- [x] #6215 — confirmed as the same historical Node 18 release path as
#6210; closed as already fixed by #6201.
- [x] #6216 — verified malformed input returns structured errors on
current main; closed as not reproducible and improved future failure
diagnostics.
- [x] #6217 — add transaction-id confirmation for large fragment
mutations after ambiguous 5xx responses; this does not make the
operation asynchronous.
- [x] #6218 — verified the existing DevTools reconnect fix from #6205;
closed as already fixed.
- [x] #6219 — preserve the incoming pathname and query when a redirect
target supplies only a hash.
- [x] #6220 — return ordered `uploaded`, `failed`, and `ambiguous`
results so partial asset successes are not lost or retried unsafely.
- [x] #6221 — verified parser recovery without a Node crash; closed as
not reproducible and improved future JSX failure diagnostics.
- [x] #6223 — verified browser-session reuse, reconnect, and cleanup;
closed as already fixed by #6205 after the reported CLI version.
- [x] #6224 — expose both text-asset sources and encode the exactly-one
requirement in the standard MCP schema.
- [x] #6225 — could not reproduce the project-specific upstream timeout;
closed with safe confirmation coverage and improved structured
diagnostics, without claiming a latency fix.
- [x] #6226 — could not reproduce the project-specific upstream timeout;
closed with safe confirmation coverage for insert-fragment, without
adding a speculative fragment-specific fix.
- [x] #6229 — reproduced the reported two-process sequence with the
current local CLI and a real browser; both captures passed, so the
0.293.0 report was closed without a speculative fix.
- [x] Require explicit user consent before preview, screenshots, diffs,
OCR, or rendered visual audits unless visual verification was requested.

## Technical notes

- Ambiguous mutation responses are confirmed only with the same
idempotent transaction id. If confirmation is also ambiguous, the
session reports `AMBIGUOUS_COMMIT`, refreshes state, and never replays a
fresh mutation.
- CLI error mapping retains the original remote error as a
non-enumerable cause, so JSON gateway status reaches the ambiguity
classifier without exposing raw remote context in serialized
diagnostics.
- Forced asset uploads are not automatically retried after an
unknown/network/5xx outcome. Batch results retain the original input
index, including duplicate filenames.
- Stable issue codes and constraints are retained for diagnostics.
Arbitrary validation paths, messages, argument values, project content,
and raw contextual data are excluded.
- The MCP handshake remains the source of tool input schemas;
goal-specific guides avoid duplicating those contracts and reduce
model-context cost.
- `update-asset-content` now exposes `path` and `content` with a
machine-readable `oneOf` constraint, matching the runtime rule that
exactly one source is required.
- Redirect targets with their own pathname or query still replace those
request components.
- Generated runtime contracts, CLI documentation, product documentation,
and deployment fixtures were regenerated from their sources.

## Verification

- [x] Three independent review/test agents exercised visual consent and
preview behavior; mutation, asset, and privacy behavior; and
accessibility, styles, and redirects.
- [x] `pnpm checks` passed after the final transport follow-up: all
workspace tests and typechecks, lint with warnings denied, package
boundaries, generated API contracts, generated docs and documentation
checks, and the complete fixture link/sync/build pipeline.
- [x] Changed-package suites passed, including Project Build (2,125
tests), HTTP client (42 tests), CLI, Protocol, and Builder.
- [x] Complete `pnpm evaluations` run on the preceding implementation
commit: all five real-world fixtures and every behavioral check passed.
Four baseline comparisons passed; one font-assets sample exceeded only
its model-output token allowance, and its isolated rerun passed.
Evaluations were not rerun after the final transport and schema
follow-ups because fresh approval is required.
- [x] Design evaluation MCP response bytes improved from the 39.8 KB
baseline to 37.6 KB after removing duplicated guide contracts.
- [x] Documentation impact reviewed; generated MCP docs and multi-file
upload guidance are updated.

## Related issues

Closes #6207

Closes #6208

Closes #6209

Ref #6210

Ref #6211

Closes #6212

Closes #6213

Ref #6214

Ref #6215

Ref #6216

Ref #6217

Ref #6218

Closes #6219

Closes #6220

Ref #6221

Ref #6223

Closes #6224

Ref #6225

Ref #6226

Ref #6229
2026-08-24 16:07:00 +02:00
Oleg Isonen 626b5e741b fix: Reduce discovery for authored fragments (#6203)
Ref #6177
Ref #6154

## Outcome

Bounded authored-fragment requests no longer receive broad
design/authentication context bundles, registry discovery, or
design-token operations through generic keyword matching.

The initial ProjectSession synchronization is unchanged. Focused target
reads, missing nested schemas, and expression guidance can still add
calls when the requested fragment requires them.

## Root cause

`meta.guide` treated the generic word `design` as visual intent and
allowed workflow-specific context bundle tools to enter ordinary
keyword-ranked recommendations. An authored component insertion could
therefore be classified as visual work and receive
`inspect-design-context` even without a supplied visual reference or a
request to reuse the project design system.

## Implementation

- Give specialized goal guides stable IDs and return structured routing
metadata with the matched workflow and broad context tools.
- Keep `inspect-auth-context` and `inspect-design-context` exclusive to
their specialized workflows.
- Treat Figma, screenshots, supplied designs, and explicit design-system
references as visual intent without treating every use of `design` as
visual work.
- Suppress component/template registry discovery for explicit
`insert-fragment` or authored-fragment requests.
- Suppress design-token operations for those requests unless the brief
explicitly asks for design-system or token reuse.
- Preserve the existing design-input workflow for Figma, screenshots,
and supplied designs.

## Todo

- [x] Add a regression test for the reported authored timer/component
workflow.
- [x] Demonstrate that the regression test fails before the routing fix
and passes afterward.
- [x] Preserve explicit design-input discovery and verification
behavior.
- [x] Add machine-readable workflow routing metadata.
- [x] Review documentation impact and update the authoritative MCP
manual plus generated GitBook output.
- [x] Review fixture impact; no fixture inputs or generated fixture
bundles are affected.
- [ ] Run the complete local agent evaluation suite. It was not run
because repository policy requires separate explicit approval and
approval was not provided.

## Verification

- Project-session MCP tests: 117 passed.
- Project-build and CLI typechecks passed.
- Targeted oxlint passed.
- Documentation generator tests and documentation typecheck passed.
- MCP documentation and CLI generated docs were regenerated twice with
stable hashes.
- `git diff --check` passed.

## Tradeoffs and limitations

- Brief classification remains deterministic intent matching; clients
still decide which recommended tools they actually call.
- The change prevents broad context tools from being recommended
generically but does not enforce a universal discovery-call limit.
- A fragment that genuinely needs existing tokens, unfamiliar
expressions, or unknown target structure can still require focused
discovery.
- No schema migration, project-data migration, or fixture regeneration
is required.
2026-08-20 13:13:45 +02:00
Oleg Isonen df3601c26a fix: Capture preview failure diagnostics (#6206)
## Summary

- Give invalid project bundles a stable `PROJECT_BUNDLE_INVALID` error
code and structured validation issues.
- Remember the latest failed MCP tool call and attach sanitized
diagnostics to automatic issue reports.
- Render the tool, error code, bundle contract, and invalid schema paths
in a dedicated issue section.

## Scope and issue assessment

This PR improves diagnostics; it does not change which asset shapes
`preview.start` accepts.

Issue #6198 came from CLI 0.290.0 and did not include the rejected
field, value, or bundle contract, so its exact root cause cannot be
established. Node 22 is not implicated by the available evidence.
Canonical video assets were added separately in #6189, which may explain
an old-CLI schema mismatch, but that relationship is an inference rather
than a confirmed reproduction.

Per project triage policy, this PR closes #6198 despite being
diagnostics-only. If the failure still exists on a current CLI, the
enriched report will provide enough evidence to reopen or file a new
actionable issue.

## Implementation

Bundle validation exposes structured paths such as `assets.0.type`. The
MCP host records the latest failure and injects it into CLI-owned
runtime metadata when `report-issue` is called.

The MCP registry supplies either a canonical known tool name or
`unknown`. Stable error codes are validated once by the CLI error-code
boundary. Detailed paths are included only for `PROJECT_BUNDLE_INVALID`,
capped at 30 issues, and omit received values, error messages, examples,
filenames, IDs, details, and stack traces. The bundle-contract field
remains optional so reports from older CLI versions still validate.

CLI-owned runtime metadata is not advertised as model-authored MCP
input, keeping the handshake compact and making ownership explicit.

## Checklist

- [x] Add structured bundle validation diagnostics.
- [x] Capture the latest MCP failure in issue-report runtime metadata.
- [x] Render deterministic diagnostics in GitHub issue bodies.
- [x] Add fail/pass regression coverage and privacy constraints.
- [x] Keep CLI-owned runtime fields out of the model-facing MCP schema.
- [x] Regenerate the server-only API contract.
- [x] Review documentation impact — no product documentation changes are
required; this only enriches internal anonymous issue reports.
- [ ] Run agent evaluations — not run because repository policy requires
explicit approval.

## Verification

- Real Node 22.22.1 stdio MCP run with an isolated malformed local
bundle returned `PROJECT_BUNDLE_INVALID`, path `assets.0.type`, code
`invalid_value`, and constraint `one_of:["image"]`.
- The following real `report-issue` call was accepted by the server and
deduplicated to existing issue #6198; it created no duplicate issue and
changed no linked project.
- Project-build: 2,115 tests passed.
- CLI: all 1,005 unaffected tests passed; one unrelated
process-supervisor timing test failed once and its exact two
owner-disconnect cases passed on immediate rerun.
- Project-build and CLI typechecks passed.
- Formatter and diff checks passed.
- All latest GitHub workflows passed, including both test/typecheck
matrices, visual regression, size and generated checks, packaged SSG,
and all six Builder E2E shards.

Closes #6198
2026-08-20 11:58:17 +02:00
Oleg Isonen f2cece41ee fix: Reconnect responsive screenshots after preview restart (#6205)
## Summary

Reconnect the reusable browser capture session and retry once when a
managed screenshot reports that its Browser DevTools connection closed.

The active generated-site preview remains running, so the retry
recreates only the failed browser session and replays the requested
single or multi-viewport capture.

## Root cause

Vision already performs a bounded recovery inside its browser runtime.
If that recovery still surfaced a DevTools session closure, the MCP
preview handler reset the failed session during error cleanup but
returned the failure to the caller. The next manual screenshot call
could succeed, but the first screenshot after a preview restart still
failed.

## Technical choices

- Give browser-session closure errors a stable typed code at the Vision
boundary.
- Detect that typed failure in the MCP preview handler.
- Close and recreate only the reusable browser capture session.
- Retry the complete capture once against the existing active preview.
- Apply the same recovery to one-viewport and responsive batch captures.
- Report the reconnect as a screenshot lifecycle progress checkpoint.

## Impact

A one-viewport responsive screenshot after preview restart now recovers
automatically from a closed DevTools connection. Other screenshot errors
retain their existing behavior, and repeated connection failure remains
bounded.

## Checklist

- [x] Add a fail-first regression for preview restart followed by a
one-viewport responsive capture
- [x] Recreate the failed browser session without restarting the active
preview
- [x] Retry the complete screenshot request once
- [x] Preserve existing behavior for non-session errors
- [x] Verify the recovery through a real preview server, Chrome, CDP,
rendering, and PNG output
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected
- [x] Review documentation impact: no update is required because this
restores documented automatic screenshot behavior
- [ ] Run agent evaluations (requires separate explicit approval)

## Verification

- Regression failed before the implementation with `Browser DevTools
connection closed.` and passed after it
- Real-browser harness with Google Chrome:
- restarted a real local HTTP preview and captured a full-page
`1440×900` PNG successfully on the first call
- forcibly closed the real CDP socket during `Page.captureScreenshot`
twice and confirmed the lower browser retry surfaced the typed
session-closed failure
- confirmed the MCP handler created a new browser session, replayed the
full capture successfully, and emitted `tool screenshot reconnecting
browser session`
  - confirmed browser recovery did not restart the active preview
- closed all Chrome processes, preview resources, temporary profiles,
and screenshot artifacts
- `pnpm --filter webstudio exec vitest run
src/commands/mcp-preview.test.ts` — 44 tests passed
- `pnpm --filter @webstudio-is/vision test` — 13 files, 81 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm --filter @webstudio-is/vision typecheck`
- `pnpm exec oxlint --deny-warnings
packages/vision/src/screenshot-browser-cdp.ts
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts`
- `pnpm exec oxfmt packages/vision/src/screenshot-browser-cdp.ts
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts`
- `git diff --check`

The DevTools failures were injected deterministically at the screenshot
command. The ordinary post-restart capture used the same real preview
and Chrome path without fault injection.

Agent evaluations were not run because repository policy requires
separate explicit approval.

Closes #6200
2026-08-20 01:02:26 +02:00
Oleg Isonen b2d62b2e49 fix: Restore scoped descendant selectors (#6195)
## Summary

Restore custom state selectors that target descendants of the selected
instance, including `:hover .button` and `:hover > .icon`.

The CSS engine already generated these selectors correctly. #5886
unintentionally rejected them while hardening MCP style inputs against
selectors that can escape instance scope.

## Technical choices

- Parse exactly one selector with `css-tree`.
- Require the selector to begin with a pseudo-class, pseudo-element, or
attribute state anchored to the selected instance.
- Allow only descendant and direct-child combinators after that anchor.
- Continue rejecting selector lists, unanchored type/class/ID selectors,
sibling combinators, unknown pseudo-selectors, and pseudo-elements used
as ancestors.
- Document CSS variables as the preferred parent-child interaction
pattern; use descendant selectors when selector-based targeting is
specifically required.

## Todo

- [x] Restore scoped descendant and direct-child selectors.
- [x] Preserve instance-scope protections.
- [x] Cover real authoring inputs and generated CSS.
- [x] Demonstrate new tests fail without the restored behavior and scope
guards.
- [x] Verify the reported workflow through the local CLI/MCP and
generated preview.
- [x] Review documentation impact and update the authoritative Builder
docs.
- [x] Verify generated API contracts remain current.

## Verification

- `pnpm --filter @webstudio-is/css-data test` — 4,623 passed, 1 skipped
- `pnpm --filter @webstudio-is/css-data typecheck`
- `pnpm --filter @webstudio-is/project-build exec vitest run
src/runtime/styles.test.ts src/runtime/audit.test.ts` — 198 passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm lint`
- `pnpm check:generated-api`
- Documentation targets and anchors verified
- Local MCP disposable-fixture workflow:
- Discovered `insert-fragment` and `update-styles` through MCP metadata.
  - Inserted a card with a descendant `<button class="button">`.
- Dry-run and committed `state: ":hover .button"`; `get-styles` returned
the persisted state.
- MCP preview generated `.chjrfuf:hover .button` while the card used the
`chjrfuf` instance class, proving the selector remained
instance-anchored.
  - Preview screenshot completed and returned HTTP 200.
- Agent evaluations were not run because the suite has no
custom-selector coverage

## Known limitation

Sibling combinators remain unsupported because they can select elements
outside the selected instance. Selector lists remain unsupported because
later list entries can escape the generated instance prefix.

Closes #6175
2026-08-20 00:20:03 +02:00
Oleg Isonen b1d23f69c0 fix: Report unsupported Node versions at CLI startup (#6201)
## Summary

- Reject unsupported Node.js versions before loading the CLI bundle.
- Report the declared engine range and detected version with an
install-and-retry instruction.
- Keep the runtime check aligned with `package.json#engines.node`.

## Root cause

The CLI declares Node.js 22.12+ but did not enforce that requirement at
startup. Package managers can warn and continue, allowing unsupported
runtimes to reach preview code. Execa 10 then initializes
`Set.prototype.union()`, which is unavailable in Node 20 and causes the
delayed `screenshot.responsive` failure.

## Technical choices

- Validate the runtime directly at the published executable boundary.
- Parse the declared engine range with `semver`.
- Dynamically import the CLI only after the runtime passes validation.
- Report the exact engine range rather than assuming every valid range
means “or newer.”
- Test the published `bin.js` in a subprocess with an injected
unsupported runtime version.
- Write the actionable startup failure to stderr and exit nonzero.
- No migrations or public API changes.

## Checklist

- [x] Add the startup runtime guard.
- [x] Add executable-level regression coverage for unsupported Node.js.
- [x] Verify the supported runtime still loads the CLI.
- [x] Review fixture impact; no fixture inputs or outputs are affected.
- [x] Review authoritative documentation; `docs/university/cli.md`
already requires Node.js 22.12+, so no update is needed.
- [ ] Run agent evaluations — not run because the requester declined
them.

## Verification

- Published and local launcher tests: 4 passed.
- CLI typecheck: passed.
- Targeted lint and formatting: passed.
- CLI production build: passed.
- Node.js 18.16.1 and 20.12.2 executable checks: exited 1 before loading
the CLI, reported the `>=22.12.0` requirement, and wrote no stdout.
- Node.js 22 executable check: CLI loaded successfully.
- Package dry run: includes the guarded `bin.js` and no separate runtime
helper.
- `git diff --check`: passed.
- The executable-level regression test was confirmed to fail against the
previous message/implementation and pass after the direct guard was
applied.

## Known limitation

An MCP client may wrap a subprocess startup failure in its own UI, but
the CLI now provides the actionable requirement and installation
instruction on stderr before any tool runs.

Closes #6190
2026-08-20 00:17:52 +02: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