6600 Commits
Author SHA1 Message Date
Oleg Isonen 47d0034640 build: Release 12-08-2026 0.290.0 2026-08-12 21:09:50 +01:00
Oleg Isonen 21ca567686 fix: Stop owned preview process trees (#6111)
## Summary

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

Closes #6019

## Technical choices

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

## Implementation checklist

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

## Verification

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

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

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

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

## Root causes and implementation

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

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

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

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

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

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

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

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

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

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

## Implementation checklist

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

## Verification

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

## Tradeoffs and limitations

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

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

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

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

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

Closes #6104
Closes #6099
Closes #6098
Closes #6106
Closes #6107
Closes #6108
Closes #6114
Closes #6115
Closes #6116
Closes #6117
2026-08-12 21:23:35 +02:00
Oleg Isonen 463a6b4fcc fix: Bind dynamic page metadata expressions (#6110)
## Summary

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

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

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

## Root cause

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

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

## Implementation

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

## Impact

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

No database migration or compatibility fallback is required.

## Todo

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

## Verification

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

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

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

Ref #6092

## Technical choices

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

## Implementation checklist

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

## Verification

- Builder diagnostics/resource tests: 27 passed.
- Asset repository tests: 39 passed.
- `git diff --check origin/main...HEAD` — passed.
2026-08-12 11:46:58 +02:00
Oleg Isonen db2d092268 ci: replace Lost Pixel with self-hosted visual regression (#6086)
## Summary

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

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

Closes #6064

## Implementation

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

## Verification

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

## Usage

Run the complete local comparison and open the visual report:

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

Focus one component while iterating:

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

Reopen the latest report without rerunning:

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

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

## Tradeoffs

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

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

## Root cause

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

## Technical choice

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

There are no migrations or public API changes.

## Todo

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

## Verification

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

## Known limitations

This intentionally stays on 2.17.5 until `sanitize-html` and the
deployment bundler have a compatible CommonJS/ESM integration.
2026-08-11 22:44:29 +02:00
Oleg Isonen 5c6ffd3148 docs: document Content Engine workflows (#6097)
## Summary

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

## Impact

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

## Checklist

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

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

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

## Why

Two gaps caused the staging export failures:

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

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

## Technical choices

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

## Todo

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

## Verification

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

Closes #6100

Supersedes #6101 after moving the change to the staging branch.
2026-08-11 20:56:12 +02:00
Oleg Isonen 11d76eb43d perf: Diagnose and reduce Assets resource latency (#6095)
## Summary

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

Ref #6092

## Technical choices

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

## Findings and measurements

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Todo

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

## Verification

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

## Known limitations

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

## Measurement gate

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

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

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

## Technical approach

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

## Compatibility

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

## Checklist

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

## Verification

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

Closes #6093
2026-08-10 21:48:32 +02:00
Oleg Isonen eb46abf4e5 fix: Recover missing preview assets (#6091)
## Summary

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

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

## Root cause

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

## Technical choices

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

## Todo

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

## Verification

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

## Known limitations

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

Closes #6090
2026-08-10 21:13:45 +02:00
Oleg Isonen db3b11970f fix: Optimize images in Markdown Embed (#6089)
## Summary

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

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

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

## Compatibility and tradeoffs

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

## Todo

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

## Verification

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

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

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

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

Closes #6053

## Root cause

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

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

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

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

## Implementation

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

## Security tradeoff

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

## Todo

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

## Verification

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

The live labeled-fork execution cannot be proven from this PR itself
because `pull_request_target` loads workflow definitions from the base
branch. It should be verified with a fork PR after this change reaches
`main`.
2026-08-10 16:15:19 +02:00
Oleg Isonen 63fcc9c5a6 fix: Improve MCP preview verification reliability (#6084)
## Summary

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

## Root causes

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

## Implementation

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

## Verification

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

Closes #6081
Closes #6080
Closes #6078
2026-08-10 11:33:25 +02:00
Oleg Isonen 8e4ee80bad fix: Report the constraining HTML ancestor (#6083)
## Summary

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

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

## Root cause

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

## Implementation

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

## Todo

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

## Verification

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

Closes #6082
2026-08-10 10:59:04 +02:00
Oleg Isonen dd316c53cc fix: Complete interrupted preview workflows (#6076)
## Summary

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

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

## Todo

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

## Verification

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

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

## Outcome

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

## Root cause

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

## Implementation

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

## Checklist

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

## Verification

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

Agent evaluations were not run because they require separate approval.
2026-08-09 01:53:31 +02:00
Oleg Isonen 41c8283a70 fix: Allow removal of invalid audited selectors (#6074)
## Summary

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

## Root cause

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

## Implementation

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

## Verification

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

Closes #6062
2026-08-09 01:46:42 +02:00
Oleg Isonen 1fe087ed26 fix: Improve MCP preview and mutation reliability (#6073)
## Summary

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

## Implementation

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

## Verification

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

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

## Compatibility note

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

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

This consolidates the reported MCP reliability fixes into one delivery.

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

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

## Technical choices

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

## Checklist

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

## Verification

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

## Known limitations

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

Closes #6034
Closes #6035
Closes #6038
Closes #6042
Closes #6043
Closes #6044
Closes #6045
Closes #6046
Closes #6047
Closes #6048
Closes #6049
Closes #6050
Closes #6051
Closes #6052
Closes #6059
Closes #6060
2026-08-08 22:22:25 +02:00
Kathir EswaranandOleg Isonen ce84ddcf12 fix: Remove hardcoded template allowlist and require authorization tokens (#6041)
## Description

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

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

## Changes

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

## Verification

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

## Before merging

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

---------

Co-authored-by: Oleg Isonen <oleg008@gmail.com>
2026-08-07 19:57:24 +02:00
Oleg Isonen 2ec78c183d feat: Add single-result modes to Assets queries (#6040)
## Summary

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

## Implementation TODOs

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

## Compatibility and behavior

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

## Verification

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

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

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

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

## Technical choices

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

## Todo

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

## Verification

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

## Known limitations

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

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

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

## Root cause

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

## Impact

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

## Todo

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

## Verification

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

Closes #5958
2026-08-07 14:19:41 +02:00
Oleg Isonen d8491ea736 fix: Make bound fields read-only (#6028)
Closes #6027

## Summary

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

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

## Implementation

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

## Checklist

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

## Verification

- `pnpm --filter @webstudio-is/builder test` — 2,509 tests passed across
217 files.
- `pnpm --filter @webstudio-is/builder typecheck` — passed.
- `pnpm lint` — passed.
- Agent evaluations were not run because they require separate explicit
approval and this change does not modify agent-facing behavior.
2026-08-07 12:34:11 +02:00
Kathir Eswaran 7b7ab69d4e chore: remove stale authz todo comments in project-router (#6036)
## Description

1. What is this PR about (link the issue and add a short description)
The rename and delete mutations already thread ctx into authorization
(edit and own permits respectively). Drops the leftover `@todo pass ctx
for authorization` comments. No behavior change.

## Code Review

- [x] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)

## Before requesting a review

- [x] made a self-review
2026-08-07 12:30:44 +02:00
Oleg Isonen b8cbd09020 fix: Preserve MCP run results on termination (#6033)
## Summary

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

## Root cause

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

## Implementation

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

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

## Checklist

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

## Verification

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

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

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

## Summary

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

## Implementation

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

## Todo

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

## Verification

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

## Compatibility and limitations

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

## Summary

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

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

## Architecture

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

## Todo

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

## Verification

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

## Deployment notes

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

## Known limitations

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

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

## Why

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

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

Ref #6007

## Verification

- [x] Regression test demonstrated failing before the implementation and
passing afterward
- [x] CLI registry/versioning tests: 3 passed
- [x] CLI typecheck
- [x] Formatting and diff checks
- [x] Documentation impact reviewed: no product-doc source change is
needed in this PR; the missed 0.286.0 snapshot was published separately
to `docs`
2026-08-05 22:03:26 +02:00
Oleg Isonen c44ef705cf perf: Speed up Builder resource loading (#6011)
## Problem

Assets-backed Builder pages can take 6–7 seconds to become usable even
when their Assets resources only request metadata. On the
production-derived Blog project, a single Assets query measured 8,703 ms
and an immediate repeat measured 8,110 ms. Four sibling Assets resources
repeated the same authorization, development-build load/parse,
compilation, canonical-asset preparation, runtime metadata load, and
document work.

## Architecture

The loader now treats remote resources holistically while preserving
provider boundaries:

- The client scheduler is resource-provider neutral. It versions logical
requests, frees capacity when obsolete requests are replaced, ignores
stale results, and aborts physical batches only when every member is
obsolete.
- Ordinary Assets requests in one server resource-loader call use a
deferred Assets provider and execute as one repository batch.
Independent HTTP, GraphQL, and other resources start immediately instead
of waiting for Assets preparation.
- Arbitrary remote responses are **not** coalesced or persistently
cached. Their auth headers, cookies, upstream freshness, and `Vary`
semantics remain isolated.
- The Assets provider authorizes and creates its API context once per
batch, then loads only `props`, `dataSources`, and `resources` from the
development Build.
- Queries covered by the saved Build compilation plan reuse that exact
plan/artifact, preserving published byte limits, truncation, and
pinned-revision behavior. Uncovered unpinned queries use a deterministic
literal union and safely fall back to their existing individual path if
shared preparation cannot preserve behavior.
- Request-scoped document loading uses npm's byte-bounded `lru-cache` so
sibling queries do not reread the same referenced author/content files.
- Sibling dynamic queries with the same filter, sort, output, and
content shape stitch into one document-graph resolution while keeping
filter values, pagination windows, results, and failures independent.
Aggregate document/byte-limit failures fall back to isolated execution.
- Provider-neutral batching lives in the SDK runtime, and project
asset-query preview orchestration lives in the shared project package so
Builder and MCP paths can reuse the same behavior.
- Cancellation flows from the client batch through the
SDK/server/build/query path, with checkpoints between expensive phases.
- Runtime asset metadata remains conservatively artifact-wide because
URL/size filters, structured references, markdown hydration,
materialized rows, and saved plans have different reachability rules. To
make 1,000-asset artifacts safe, PostgREST `.in(...)` lookups are
deduplicated and split into 100-ID chunks with bounded concurrency.

## Implementation checklist

- [x] Trace the production Blog resource path and identify repeated work
- [x] Copy the production Blog project into an ignored disposable local
project
- [x] Record production and local baselines
- [x] Add provider-neutral client scheduling, stale-result protection,
and cancellation
- [x] Batch ordinary Assets queries without coupling or caching
HTTP/GraphQL responses
- [x] Load only Build fields required to create the saved content
compilation plan
- [x] Preserve saved-plan, truncation, fallback, diagnostics, and
revision semantics
- [x] Share Assets artifacts, runtime metadata, and revision-keyed
document reads per request
- [x] Stitch compatible query shapes into one graph resolution with
isolated fallback
- [x] Extract reusable SDK batching and project preview orchestration
for MCP/runtime consumers
- [x] Bound large runtime asset metadata lookups for the 1,000-asset
project limit
- [x] Add regression coverage and demonstrate new tests failing before
their fixes
- [x] Run focused and broad tests, type checks, lint, format,
package-boundary, and diff checks
- [x] Review documentation impact; no authoritative `docs/` content
changes are required
- [x] Regenerate and verify the affected asset-query runtime fixture; no
production data is changed
- [x] Address all actionable PR review comments
- [x] Rerun the final parity-preserving implementation against the
copied Blog project

## Measurements

Production trace before the change:

- Updates Assets query: 8,703 ms
- Immediate identical repeat: 8,110 ms
- Query artifact: 9,344 bytes / 7 documents
- Saved source database: 42,086 bytes / 28 documents

Copied-project local baseline:

- Builder navigation until resource loading: 5,111 ms
- Exact four-resource batches: 1,448 / 797 / 1,377 / 774 / 3,018 ms

During implementation, before the final saved-plan parity tightening:

- Exact four-resource batch: 174 ms first run, then 51–80 ms
- Actual Builder resource-loader requests: 234–246 ms

Final parity-preserving copied-project rerun:

- Preflight: 10 ms
- Exact four-resource batch: 435 ms cold, then 119 / 97 / 90 / 79 ms
warm
- Every response returned HTTP 200 with the same 16,884-byte payload

The final cold batch is 70% faster than the best copied-project cold
baseline, and the fastest warm batch is 90% faster than the best warm
baseline. The production trace remains the authoritative production
baseline; these before/after comparisons use the same disposable local
project and captured request payload.

## Verification

Passed:

- Full package suites: Builder (212 files / 2,496 tests), asset-uploader
(22 / 193), content-engine (40 / 274), project (7 / 84), SDK (29 / 508),
and project-build (82 / 2,070)
- Final focused regressions: Builder preview/API/resource integration (3
files / 42 tests), SDK resource loader (23 tests), and document-graph
resolution (6 tests)
- Builder, asset-uploader, content-engine, project, SDK, and
project-build type checks
- Fixture regeneration is deterministic; the generated vendor exactly
matches the source runtime bundle
- Fixture typecheck and production build
- Changed-file Oxfmt, repository Oxlint, and package-boundary checks
- `git diff --check`
- `git diff --name-only HEAD --exit-code`

New behavioral tests were demonstrated to fail before implementation and
pass afterward, including Assets batch coordination, saved-plan wiring,
truncation and pinned revisions, shared document reads, cancellation
checkpoints, stale/replaced client requests, and large PostgREST ID
filters.

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

## Compatibility and remaining work

The public resource result/error shape and on-demand diagnostics
contract stay compatible. Generic HTTP/GraphQL caching semantics are
unchanged. Compilation already in progress is not force-cancelled
because safely interrupting shared compilation requires
ownership/reference tracking; cancellation prevents later phases from
starting.

Closes #6003
2026-08-04 18:39:41 +02:00
Oleg Isonen 6385e290c6 feat: Add syntax highlighting to Code Text (#6007)
Closes #4119

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

## Approach

Code Text uses one optional public highlighting boundary:

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

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

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

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

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

## Bundle impact

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

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

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

## Tradeoffs and compatibility

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

## Todo

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

## Verification

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

## Summary

Preserve machine-readable scalar constraints for every optional MCP
input property advertised through `tools/list`.

MCP clients now receive the declared string, integer, and boolean types,
along with bounds, enums, defaults, and related scalar constraints,
instead of empty property schemas.

## Root cause

The compact SDK handshake intentionally retained full scalar schemas for
required fields and destructive-safety controls, but replaced ordinary
optional property schemas with `{}`. Clients therefore lacked the type
information needed to serialize values such as `limit`, `verbose`, and
`maxDepth` correctly.

## Implementation

- Reuse the existing compact scalar-schema projection for every
advertised optional property.
- Keep descriptions and large structured contracts out of the initial
handshake.
- Preserve the existing focused-discovery behavior for complete schemas.
- Add protocol-level regression coverage through the official MCP SDK
for string, integer, and boolean optional inputs.

## Todo

- [x] Preserve optional MCP property types and scalar constraints.
- [x] Add regression coverage for representative string, integer, and
boolean inputs.
- [x] Demonstrate the regression test fails before the fix and passes
afterward.
- [x] Run project-build and CLI MCP/schema tests.
- [x] Run relevant typechecks, lint, formatting, and package-boundary
validation.
- [x] Review fixture impact; no fixture inputs or generated fixture
bundles are affected.
- [x] Review documentation impact; existing documentation describes the
intended typed schema behavior, so no documentation update is required.
- [ ] Run agent evaluations after separate explicit approval.

## Verification

- `@webstudio-is/project-build`: 2,077 tests passed.
- CLI MCP/schema tests: 51 passed.
- Project-build and CLI typechecks passed.
- Repository lint passed.
- Changed-file formatting passed.
- Package-boundary validation passed.
- `git diff --check` passed.
- The protocol regression assertion failed against the previous
implementation and passed after the fix.

Agent evaluations were not run because repository policy requires
separate explicit approval.
2026-08-04 17:46:02 +02:00
Oleg Isonen 08e1e19b3f fix: expose existing text bindings in mixed content (#6008)
Closes #5992

## Summary

Expose the existing **Text content** binding control when a directly
text-capable selected instance contains expressions alongside other
content.

The supplied reading-time span keeps its existing child sequence:

- static prefix text
- bound expression

No data migration or new Settings editor is introduced. Normalization
happens only when the user explicitly resets a binding.
Expression-bearing nodes are excluded from canvas text editing because
the canvas editor cannot represent bindings safely. Literal descendants
in mixed trees remain canvas-editable, while bindings remain editable
through Settings.

## Implementation

- Reuse the existing scalar Text content and binding UI.
- Target expression children with indexed `instances.updateText`
mutations, preserving instance boundaries and unrelated siblings.
- Fail closed when the selected instance is missing or no longer has a
safe text target.
- Evaluate expressions through the binding control's existing live
scope.
- Centralize text-content target classification in project-build for
reuse by the engine and Builder.
- Reset every expression atomically through the engine's
`instances.setTextContent` operation, validate the complete replacement
set, and coalesce adjacent text nodes without crossing nested-instance
boundaries.
- Centralize expression-free canvas text-editor targeting in the
project-build lookup module.
- Route double-click, Enter, post-insert editing, cursor generation, and
keyboard navigation through the shared lookup rules.
- Exclude single-expression and mixed/nested expression targets from the
canvas editor in every mode.
- Preserve canvas editing for literal descendants inside otherwise mixed
content.
- Share canvas fixtures and simplify binding-removal capability to an
optional handler.

## Verification

- [x] Settings exposes and edits mixed text/expression targets without
replacing siblings.
- [x] Reset binding converts every expression on the instance and
normalizes adjacent text.
- [x] Missing, unsupported, stale, and incomplete replacement sets fail
closed.
- [x] Double-click and Enter do not open bound nodes in the canvas
editor.
- [x] Literal descendants within mixed trees remain canvas-editable.
- [x] Complete Builder suite passes (2,497 tests).
- [x] Complete project-build suite passes (2,070 tests).
- [x] New targeted Builder and project-build regression tests pass (175
tests).
- [x] Builder and project-build typechecks pass.
- [x] Repository lint passes.
- [x] Package-boundary validation passes.
- [x] Generated runtime operation contracts are updated from source.
- [x] Fixture inputs and outputs are unaffected.
- [x] Documentation impact reviewed: no documentation update is needed.
- [ ] Interactive browser smoke test was unavailable because the in-app
browser failed to initialize.
- [ ] Agent evaluations were not run because they require separate
approval.

## CI note

The previous head's Main workflow failed while pulling the E2E database
image with Docker Hub `toomanyrequests: Rate exceeded`. Lost Pixel
failed separately with a certificate error. Both are external
infrastructure failures; branch formatting, lint, package boundaries,
generated contracts/docs, tests, and typechecks were successful in that
run.

Supersedes #5993, which GitHub closed automatically when its head branch
was renamed.
2026-08-04 15:36:19 +02:00
Oleg Isonen 17947e47fa fix: Constrain the asset settings popover width (#6002)
## Summary

- Set the asset settings popover to a fixed 250px width using the
existing `css` prop.

## Root cause

The popover used a minimum width while its shared content style allowed
intrinsic `max-content` sizing. Long descriptions could therefore expand
the popover beyond the intended layout.

## Impact

Asset settings now stay consistently sized, and long descriptions remain
contained within the popover.

## Todo

- [x] Constrain the asset settings popover width.
- [x] Keep styling on the existing `css` prop.
- [x] Avoid a constant-only regression test, in line with the existing
agent testing guidance.
- [x] Confirm fixture inputs and outputs are unaffected.
- [x] Review documentation impact; no user-facing documentation changes
are needed.
- [x] Run targeted tests, formatting, linting, and Builder typecheck.

## Verification

- `pnpm --filter=@webstudio-is/builder test -- asset-settings.test.tsx`
- `pnpm exec oxfmt --check
apps/builder/app/builder/shared/asset-manager/asset-settings.tsx`
- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/shared/asset-manager/asset-settings.tsx`
- `pnpm --filter=@webstudio-is/builder typecheck`

Closes #6001
2026-08-04 12:34:59 +02:00
Oleg Isonen 1ca01e96a3 docs: Guide MCP agents to build dynamic JSON-LD (#5998)
## Summary

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

## Implementation

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

## Verification

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

Closes #5997
2026-08-03 23:36:03 +02:00
Oleg Isonen 74d88a3fb2 fix: Resolve structured Asset paths in queries (#5995)
## Summary

Resolve portable relative Asset paths stored in Markdown frontmatter and
structured JSON properties to canonical runtime Asset URLs inside the
content engine.

The authored source remains unchanged. Asset resource consumers now
receive canonical URLs without Image, CSS, JSON-LD, or other
binding-specific transformations.

## Root cause

Markdown body links already recorded relative Asset references for
runtime rewriting, but structured values bypassed that pipeline.
Browsers therefore resolved values such as `./assets/hero.png` against
the page URL instead of the source document folder.

## Implementation

- Recursively discover unique structured Asset references using the
existing canonical path resolver and the platform URL parser.
- Store optional `assetValueReferences` in content artifacts and runtime
projections.
- Include referenced Assets in publication/runtime dependency collection
and validation.
- Resolve values before filtering, sorting, and projection in normal and
document-graph queries.
- Rewrite materialized query items at runtime without mutating canonical
documents or compiled rows.
- Merge authored query parameters and fragments with canonical Asset
URLs.
- Keep queries whose filter/sort semantics depend on runtime Asset URLs
out of compile-time materialization.
- Add an explicit content-compilation cache generation so pre-feature
artifacts are recompiled.
- Refresh fixture bundle versions and the generated asset-query runtime.
- Document relative references in Markdown frontmatter and JSON content.

## Checklist

- [x] Add structured reference discovery for nested objects and arrays
- [x] Support Markdown frontmatter and JSON document properties
- [x] Preserve URL query strings and fragments
- [x] Thread references through build/runtime artifacts and publication
dependencies
- [x] Resolve normal, materialized, and document-graph query results
- [x] Apply resolved values to filtering, sorting, and projection
- [x] Preserve compatibility with artifacts that omit the optional field
- [x] Add fail/pass regression coverage
- [x] Run package tests, typechecks, lint, and generated API checks
- [x] Regenerate and build all fixtures
- [x] Review documentation impact and update the Assets guide

## Verification

- `pnpm --filter @webstudio-is/content-engine test` — 269 passed
- `pnpm --filter @webstudio-is/content-engine typecheck` — passed
- `pnpm --filter @webstudio-is/asset-uploader test` — 184 passed
- `pnpm --filter @webstudio-is/asset-uploader typecheck` — passed
- `pnpm --filter @webstudio-is/protocol test` — 88 passed
- `pnpm lint` — passed
- `pnpm check:generated-api` — passed; generated files clean
- `pnpm --filter webstudio build:content-runtime && pnpm -r
fixtures:build` — passed for all fixtures
- Assets documentation — internal links resolve and `git diff --check`
passes
- Regression proof: neutralizing structured discovery/replacement caused
8 expected failures across source compilation, standard queries,
document-graph queries, and materialized queries; restoring the
implementation passed all tests.
- `pnpm evaluations` — not run because explicit approval is required.

## Compatibility

The artifact field is optional, so older artifacts continue to parse and
execute. Existing absolute, protocol-relative, root-relative,
fragment-only, query-only, external-scheme, malformed, missing, and
ambiguous values remain unchanged.
2026-08-03 23:11:59 +02:00
Oleg Isonen 1f4095cf9a fix: Improve generated MCP documentation (#5991)
## Summary

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

## Root cause

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

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

## Verification

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

Fixture inputs and outputs are unaffected.

## Todo

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

Ref #5986
2026-08-03 20:21:35 +02:00
Oleg Isonen e6a29b5d36 fix: Show released CLI version in MCP docs (#5990)
## Summary

- make the MCP page generator render the CLI package version directly in
the page title
- publish `Webstudio MCP v<release>` from the exact generated page in
each successful release
- merge each successful release tag into the publication-only `docs`
branch
- serialize publication and ignore reruns or older release tags already
contained in `docs`

## Root cause

The first publication design advanced `docs` to the release commit
without putting the CLI package version into the generated page. The
repository uses `0.0.0-webstudio-version` until release time, so the
release workflow now replaces that existing version placeholder with the
successful tag version before publishing.

## Todo

- [x] Generate the CLI version in the MCP page title.
- [x] Publish the generated page from the exact release revision.
- [x] Make publication idempotent and prevent version downgrades.
- [x] Validate generation, formatting, linting, and release-branch
behavior.
- [ ] Merge this pull request.
- [ ] Publish `0.285.0` as the initial version on the existing `docs`
branch.

## Verification

- `pnpm docs:mcp:test`
- `pnpm docs:mcp:typecheck`
- `pnpm check:generated-docs`
- `pnpm lint`
- `pnpm oxfmt --check`
- `git diff --check`
- isolated Git simulation for release merge, exact generated-page
selection, version replacement, and rerun detection
- regression tests demonstrated failing without version rendering and
validation, then passing after restoration

Ref #5986
0.285.1
2026-08-03 18:32:59 +02:00
Oleg Isonen 99371187dc docs: Move product documentation into Builder (#5987)
## Summary

- move the complete GitBook documentation tree and assets into Builder
under `/docs`
- generate the exhaustive MCP page from the local CLI source in the same
change
- publish GitBook from a release-gated `docs` branch containing the
exact successfully released revision
- run MCP documentation tests, typechecking, and stale-output checks
with the existing generated-docs check
- remove links to the Community Discussions repository

## Technical choices

- Import the current documentation as a linear snapshot so the branch
satisfies Builder's required-linear-history protection.
- Keep the Markdown wrapper separate from the TypeScript generator at
`scripts/docs/mcp-page.md`.
- Generate through `packages/cli/local.js` so CLI guidance and its
documentation are reviewed together.
- Let `check:generated-docs` reject CLI changes that leave the committed
MCP page stale.
- After both GitHub release creation and package publication succeed,
fast-forward the read-only `docs` branch to the immutable release tag
commit. A divergent update fails instead of publishing a different
revision.
- Configure GitBook to publish `/docs` from the `docs` branch, not
directly from `main`.
- Exclude `/docs` from the repository-wide `oxfmt` scan so imported
GitBook Markdown retains its established formatting; generator tests and
stale-output checks still validate the MCP page.
- Exclude `transcripts/` and GitHub Discussions from the migration.

## Todo

- [x] Import documentation, navigation, includes, and assets under
`/docs`.
- [x] Generate the MCP page from the local CLI source.
- [x] Move MCP generation and validation into Builder.
- [x] Require MCP documentation updates alongside CLI changes.
- [x] Publish released revisions through a read-only `docs` branch.
- [x] Remove Community repository links from Builder and the migrated
docs.
- [x] Preserve and verify a final Community Git bundle locally.
- [ ] Merge this pull request.
- [ ] Let the next successful release create or advance the `docs`
branch.
- [ ] Repoint GitBook Git Sync to `webstudio-is/webstudio`, branch
`docs`, root directory `/docs`.
- [ ] Verify `docs.webstudio.is`, navigation, assets, anchors, and the
MCP page in production.
- [ ] Delete `webstudio-is/webstudio-community`.

## Verification

- `pnpm check:generated-docs`
- `pnpm lint`
- `pnpm oxfmt --check`
- `git diff --check origin/main...HEAD`
- generator regression test demonstrated failing when the GitBook
wrapper behavior was removed, then passing after restoration
- no fixture inputs or outputs are affected

## Known limitation

All documentation merged after the previous release waits for the next
successful release before GitBook publishes it. This deliberately keeps
the public MCP reference aligned with an installable CLI revision.

Ref #5986
2026-08-03 17:55:50 +02:00
Oleg Isonen cf0e76f96a fix: Validate authored fragments against HTML content models (#5988)
## Summary

Prevent authored fragment insertion from creating invalid HTML
structures while preserving the Builder's warning-only behavior for
legacy clipboard content.

Also show an explicit loading state while Assets resource diagnostics
are fetched lazily, instead of briefly reporting that no diagnostics are
available.

Allow established text assets to be edited without their preserved
creation timestamp being mistaken for an expired upload reservation.

## Root cause

The shared insert-fragment runtime mutation did not validate the final
HTML content model.

Builder copy and paste uses the same mutation for legacy project
content. The compatibility path must tolerate only violations already
present in the copied fragment while still rejecting new violations
caused by its destination.

The diagnostics inspector had no pending state, and an unkeyed pending
state could be cleared by an older request after the inspected resource
changed.

Asset revisions preserve the original file's `createdAt` value, but
upload finalization used `createdAt` to enforce the 30-minute
upload-ticket lifetime.

## Implementation

- [x] Enforce HTML content-model validation at the shared
fragment-insertion mutation boundary.
- [x] Centralize fragment placement warning collection with the existing
matcher logic.
- [x] Keep the legacy compatibility switch out of the public runtime/MCP
contract.
- [x] Pass compatibility through trusted Builder runtime context only.
- [x] Ignore only violations already present in legacy clipboard content
and reject new destination violations.
- [x] Preserve warning-only behavior across the shared Builder paste
helper and instance-copy path.
- [x] Key diagnostics loading state to the active resource request.
- [x] Validate upload-ticket freshness with `updatedAt` while preserving
logical asset `createdAt`.
- [x] Regenerate the runtime operation contract.
- [x] Add regression coverage for strict insertion, legacy
compatibility, destination validation, paste context propagation,
diagnostics request races, and established text assets.

## Impact

MCP and other public authored-fragment callers remain strictly validated
and cannot opt out.

Legacy Builder clipboard content remains pasteable with warnings, but
compatibility cannot introduce a new violation at the selected
destination.

Assets diagnostics retain their loading state when an older request
settles, and established text assets can be updated after 30 minutes
without changing their displayed creation date.

## Verification

- [x] Project-build targeted tests: 80 passed.
- [x] Builder targeted tests: 86 passed.
- [x] Asset-uploader tests: 184 passed.
- [x] Builder typecheck passed.
- [x] Project-build typecheck passed.
- [x] Asset-uploader typecheck passed.
- [x] Generated runtime operation contracts are current.
- [x] New regression tests were demonstrated failing without their fixes
and passing after restoration.
- [x] Fresh GitHub Main workflow passed, including both test/typecheck
matrices and all six Builder E2E shards. Shard 3 was rerun successfully
after Docker Hub rate-limited its initial database-image pull.

## External CI status

- Lost Pixel failed on an external TLS certificate handshake after
Storybook built successfully.
- The staging deployment workflow failed before executing job steps,
independently of code checks.
2026-08-03 17:38:17 +02:00
Oleg Isonen 5cefbc0320 perf: Speed up Builder resource loading (#5980)
## Summary

Remove avoidable latency from Builder resource loading and make Assets
resources use the content database efficiently.

## Why

Builder currently applies a one-second debounce to every uncached
resource request. Assets queries also perform server-side preparation
work that should be reused or deferred, making pagination and route
changes noticeably slower than expected.

## Implementation plan

- [x] Dispatch the complete set of currently computable page resources
without the one-second debounce.
- [x] Preserve batching, deduplication, exact-request caching,
invalidation, and bounded queue draining.
- [x] Add regression tests for immediate dispatch, batching,
deduplication, invalidation, and queue draining.
- [x] Cache compiled Assets content databases by the relevant
project/build and asset revision.
- [x] Run pagination, filtering, sorting, and metadata selection against
the cached database without rebuilding it for each request.
- [x] Fetch only referenced document content that is required by the
query and keep graph reads parallel.
- [x] Avoid generating and returning full diagnostics artifacts for
ordinary canvas resource loads; load detailed diagnostics on demand.
- [x] Preserve distinct loading and confirmed-empty states for article
and author resources.
- [x] Add coverage for paginated overview and article navigation
behavior.
- [x] Deep-review the complete change and remove redundant plan
construction, duplicated JSON comparison, path-based diagnostics
inference, and synthetic canvas diagnostics.
- [x] Make resource and diagnostics invalidation race-safe, including
invalidation during in-flight requests.
- [x] Run formatting, tests, lint, type checking, package-boundary
checks, and affected fixtures.

## Verification

- `pnpm oxfmt --check`
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm --filter @webstudio-is/content-engine test` (262 tests)
- `pnpm --filter @webstudio-is/asset-uploader test` (184 tests)
- `pnpm --filter @webstudio-is/builder test` (2,483 tests)
- Typechecks for Content Engine, Project Build, Asset Uploader, and
Builder
- `pnpm fixtures` (passed; no fixture diff)

The new invalidation regression tests were verified to fail against the
previous behavior and pass with the fix. Agent evaluations were not run
because they require separate approval.

## Known limitations

None identified.
2026-08-03 01:15:56 +02:00
Oleg Isonen e855a1e7cf fix: Skip unenumerated dynamic SSG pages (#5982)
## Outcome

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

Closes #5981

## Root cause

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

## Implementation

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

## Todo

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

## Verification

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

## Notes

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

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

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

## Technical choices and compatibility

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

## Todo

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

## Verification

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

## Known limitations

Synthetic drag-and-drop behavior is unchanged. The new menu and existing
keyboard shortcuts provide the automatable and accessible reordering
path. Agent evaluations were not run because explicit approval was not
provided.
2026-08-03 00:36:56 +02:00
Oleg Isonen aefe532a73 fix: Preserve instance deep links on Builder reload (#5977)
## Summary

Preserve exact Builder instance selections during initial load,
authentication, and hard reload, including shared content rendered
through multiple Slot instances.

## Implementation

- [x] Store the complete selector in one comma-separated `instance` URL
parameter.
- [x] Reuse `canResolveInstanceSelector` to validate every selector path
before restoring it.
- [x] Resolve the selected page from the selector root.
- [x] Wait for the authoritative Builder data-loaded state before
applying initial URL state.
- [x] Keep selection-to-URL synchronization paused until initial URL
state is resolved.
- [x] Let the existing auth loader derive `returnTo` directly from
Builder requests.
- [x] Update generated Builder links, including pre-publish audit links,
to use full selectors.
- [x] Add regression coverage for incomplete sync data and duplicate
shared-slot child IDs.

## Verification

- [x] A fresh browser session authenticates and opens a direct selector
deep link.
- [x] Hard reload retains the exact selector URL and selection.
- [x] Clicking the first shared child creates an `instance` URL for the
first Slot path; reload restores the first occurrence.
- [x] Clicking the second shared child creates a distinct `instance` URL
for the second Slot path; reload restores the second occurrence.
- [x] Focused Vitest suite: 8 tests passed.
- [x] Builder TypeScript typecheck.
- [x] Focused oxlint.
- [x] Focused oxfmt check.
- [x] `git diff --check`.

## Compatibility and limitations

No schema, migration, or public API changes. This change uses one
canonical full-selector URL format; ID-only deep links are not retained
as a compatibility path.
2026-08-03 00:16:26 +02:00
Oleg Isonen 9fe63c7501 fix: Submit webhook forms only once (#5979)
## Summary

Webhook Form action resources were being persisted both as prop-bound
actions and render-time data sources. Generated sites could therefore
execute the POST while rendering and again when the visitor submitted
the form.

This change keeps prop-bound resources action-only and hardens
generation for existing builds that still contain a stale data-source
binding.

Ref #5806

## Implementation

- Keep `resources.upsertProp` resources out of render-time data and
remove stale exposure when an action is edited.
- Make the mutation's `dataSourceId` result optional and regenerate the
runtime contract.
- Exclude action-bound resources from the generated data-resource map,
preserving them only in the action map.
- Extend the Webhook Form E2E with a local request-counting endpoint.

## Todo

- [x] Prevent new Form actions from creating render-time data sources.
- [x] Protect existing persisted builds from loading action resources
during rendering.
- [x] Regenerate the runtime operation contract.
- [x] Add mutation, generation, Builder-store, and generated-site
regression coverage.
- [x] Verify zero calls on page load and one populated POST on submit.

## Verification

- [x] Focused Builder E2E: `Webhook Form action submits once and
persists after reload`
- [x] Project Build test suite: 2,062 tests
- [x] SDK test suite: 505 tests
- [x] Builder resource mutation test file: 25 tests
- [x] SDK, Project Build, and Builder typechecks
- [x] Changed-file lint, formatting, and diff checks
- [x] Generated API cleanliness check
- [ ] Agent evaluation suite (not run; requires explicit approval)

## Notes

No fixture inputs or generated fixture bundles are affected.
2026-08-02 22:56:08 +02:00
Oleg Isonen 22642fc90b fix: Show publish feedback immediately (#5978)
## Summary

Move the optimistic publishing state update ahead of the asynchronous
content-database diagnostics request. The publish button now shows its
pending animation immediately after a valid click instead of waiting for
that network round trip.

## Root cause

The content-database publish diagnostics were added before the function
that set `isPublishing`. Any latency in that request left the publish
button looking idle.

## Implementation

- Start the optimistic publishing state at the beginning of the publish
transition.
- Preserve the existing pre-publish audit, content-database warning,
publish request, polling, and error behavior.

## Todo

- [x] Trace the publish click and pending-state sequence.
- [x] Move pending feedback ahead of asynchronous diagnostics.
- [x] Run targeted lint and Builder typecheck.
- [x] Run the complete Builder test suite.

## Verification

- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/features/publish/publish.tsx`
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm --filter @webstudio-is/builder test` — 211 files, 2,472 tests
passed

No fixture inputs or generated outputs are affected.

Closes #5976
2026-08-02 17:13:23 +02:00
Oleg Isonen ca08919b13 fix: Confirm conflicting root styles on page paste (#5971)
## Summary

Prevent page, template, folder, and marketplace page imports from
silently replacing local declarations on the shared global root. When
incoming root styles differ at the same property, breakpoint, and state,
Builder now asks whether to preserve the project values or use the
incoming values. Cancellation happens before the runtime mutation.

## Technical choices

- Compare CSS values by their serialized meaning so equivalent keyword
and unparsed values do not create false conflicts.
- Plan breakpoint remapping with the same insertion path used by
fragment paste, including ID collisions and breakpoint limits.
- Keep named-token conflict resolution separate because token conflicts
support suffixing and merging, while a shared root-local declaration has
only an existing or incoming value.
- Default the root-style dialog to **Keep existing** because global root
styles affect every page, and show the breakpoint plus current and
incoming values for each conflict.
- Re-read current project data after the asynchronous token dialog
before detecting root conflicts.
- Leave no-conflict resolution undefined so the runtime performs a final
conflict check instead of treating it as an explicit **Use incoming**
choice.
- Resolve root conflicts at the page-copy boundary before using the
generic fragment/style insertion path.
- Reuse one root-conflict UI resolver for clipboard paste and
marketplace imports, one page-transfer traversal helper in the UI and
runtime, and one presentational shell for token and root-style conflict
dialogs.
- Require an explicit `rootStyleConflictResolution` for conflicting
`pageTransfer.insert` and `pages.copy` runtime operations so non-UI
callers cannot overwrite global values accidentally.
- Document that `ours` keeps target-project values and `theirs` uses
incoming values in both generated runtime contracts.

## Todo

- [x] Detect conflicting root-local declarations by property,
breakpoint, and state
- [x] Add keep-existing, use-incoming, and cancel behavior
- [x] Cover page, template, nested-folder, marketplace, and direct
runtime transfer paths
- [x] Preserve non-conflicting incoming root declarations with either
resolution
- [x] Avoid false conflicts for equivalent CSS values and colliding
breakpoint IDs
- [x] Revalidate root styles after asynchronous token resolution
- [x] Preserve existing named-token conflict behavior
- [x] Remove duplicated transfer traversal, conflict resolution mapping,
and dialog layout
- [x] Regenerate the runtime operation contract
- [x] Verify regression tests fail when their corresponding behavior is
disabled
- [x] Run targeted tests, typechecks, formatting, and lint

## Verification

- `pnpm --filter @webstudio-is/project-build test --
src/runtime/style-copy.test.ts src/runtime/fragment.test.tsx
src/runtime/page-copy.test.tsx` — 80 passed
- `pnpm --filter @webstudio-is/builder test --
app/shared/copy-paste/plugin-page.test.ts
app/shared/root-style-conflict-dialog.test.tsx
app/shared/token-conflict-dialog.test.tsx` — 17 passed
- Retrospective fail/pass audit covered semantic CSS equality,
breakpoint ID collisions, both runtime guards, both resolution
directions, folder/template paths, conflict detail rendering, and stale
target data after token resolution
- `pnpm --filter @webstudio-is/project-build typecheck` — passed
- `pnpm --filter @webstudio-is/builder typecheck` — passed
- `pnpm lint` — passed
- Runtime contract regeneration was repeated and produced identical
output
- Fixture pipeline not run locally: no fixture inputs or outputs are
affected; GitHub fixture checks passed on the previous revision
- Agent evaluations not run: repository guidance requires separate
explicit approval before running them

## Compatibility and limitations

The new runtime inputs are optional for non-conflicting imports.
Existing imports continue unchanged; only imports that would overwrite a
conflicting shared root-local declaration require an explicit
resolution. No migrations or persistence changes are required.

Closes #5970
2026-08-02 12:33:02 +02:00
Oleg Isonen f335340a54 fix: Correct content database sizing and Markdown references (#5972)
## Summary

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

## Root cause

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

## Impact

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

Ref #5966

## Verification

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