6580 Commits
Author SHA1 Message Date
Oleg Isonen 9e92c5f926 build: Release 07-08-2026 0.288.0 2026-08-07 19:16:35 +01: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
Oleg Isonen 56a22fde8a feat: Add storage-backed document graph resolution (#5966)
## Summary

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

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

## Implementation todos

### Document graph foundation

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

### Build-time graph

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

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

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

### Document representations

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

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

### Runtime resolution

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

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

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

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

### Product integration

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

### Review hardening

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

### Verification

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

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

## Intended outcome

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

## Verification results

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

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

## Implementation

### Marketplace and MCP

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

### Assets database

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

### Release

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

## Todo

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

## Verification

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

## Notes

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

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

## Technical choices

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

## Todo

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

## Verification

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

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

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

## Verification

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

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

## Outcome

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

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

## Final architecture

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

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


Query authoring is a separate OpenAPI-driven path:

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

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

### Activation and compilation

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

### Runtime database limit

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

### Snapshot consistency

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

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

### Package ownership

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

### Runtime lifecycle

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

## Implemented foundation

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

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

## Remaining implementation plan

### 1. Establish the content engine

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

### 2. Define source snapshots and compilation plans

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

### 3. Separate storage from content processing

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

### 4. Build deterministic compilation

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

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

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

### 6. Finalize the runtime database

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

### 7. Make query authoring OpenAPI-driven

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

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

### 8. Manage ContentDatabase lifecycle in Builder

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

### 9. Materialize and verify immutable builds

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

### 10. Integrate all targets

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

### 11. Correct API and package ownership

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

### 12. Remove superseded deployment infrastructure

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

### 13. Verification and release readiness

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

## Future MDX work

MDX is intentionally separate from this release.

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

## Superseded design

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

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

## Verification baseline

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

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

Local verification baseline:

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

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

## Release constraints

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

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

## Root causes and impact

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

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

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

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

## Workflow-churn iteration

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

Current deterministic workflow shape:

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

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

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

## Style response compaction

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

Focused evaluation results:

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

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

## Goal-guide context compaction

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

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

Final complete-suite results:

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

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

## Responsive verification batching

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

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

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

Guarded accepted results:

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

## Design and font workflow batching

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

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

Guarded accepted improvements versus the previous responsive-batching
baseline:

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

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


## Terminal responsive verification

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

Latest complete comparison against the accepted baseline:

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

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

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

## Accepted evaluation baseline

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

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

## Verification

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

## Implementation checklist

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

## Summary

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

## Root causes

### External screenshot lifecycle

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

### Validation errors and schemas

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

### Asset metadata persistence

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

### Page text expression disambiguation

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

### Evaluation observability

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

## Impact

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

## Todo

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

## Verification

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

Allow MCP `update-props` callers to configure `animationAction`
keyframes with normal CSS strings, including values such as `transform:
"translateX(-75%)"`.

Use one SDK-owned animation authoring schema in both the Builder UI and
MCP so CSS parsing, timing defaults, and validation cannot drift between
entry points.

## Root cause

The Builder UI and MCP did not share an authoring contract. Builder
controls produced Webstudio's structured style values and animation
factories supplied timing objects, while MCP passed its input directly
toward the persisted prop schema. As a result, normal CSS strings failed
deep Zod validation and omitted timing objects were rejected.

## Implementation

- Add a shared `createAnimationActionInput` schema factory to the SDK.
- Inject the CSS parser into the schema factory to avoid adding an `sdk`
→ `css-data` dependency cycle.
- Use the shared schema in both Builder animation validation and MCP
`update-props`.
- Accept raw CSS strings or existing structured Webstudio values in
`keyframes[].styles`.
- Parse string values before persistence and default omitted animation
`timing` to an empty options object.
- Update Animation Group guidance and regenerate the runtime operation
contract.
- Preserve strict validation for existing structured animation style
values.

## Checklist

- [x] Add regression coverage for the reported horizontal scroll
payload.
- [x] Verify the regression fails before implementation and passes after
it.
- [x] Add SDK coverage for shared animation authoring normalization.
- [x] Use the shared authoring schema in Builder and MCP.
- [x] Verify package boundaries and generated runtime contracts.
- [x] Run affected typechecks, targeted tests, lint, and formatting.
- [x] Run the complete agent evaluation suite.
- [ ] Resolve evaluation read-budget failures: `authenticated-page-v1`
and `design-input-v1` failed only `boundedReads`.

## Validation

Passed:

- `pnpm --filter @webstudio-is/sdk test --
src/schema/animation-schema.test.ts`
- `pnpm --filter @webstudio-is/project-build test --
src/runtime/props.test.ts src/mcp.test.ts`
- `pnpm --filter @webstudio-is/sdk typecheck`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm check:package-boundaries`
- `pnpm exec oxlint --deny-warnings <changed source and test files>`
- `pnpm exec oxfmt --check <changed source and test files>`
- `pnpm check:generated-api`
- `font-assets-v1` agent evaluation

Agent evaluation findings:

- `authenticated-page-v1`: failed only `boundedReads`; all functional,
audit, binding, privacy, and visual checks passed.
- `design-input-v1`: failed only `boundedReads`; all functional, audit,
responsive, design-system, privacy, and visual checks passed.
- `font-assets-v1`: passed all checks.
2026-07-29 17:37:56 +02:00
Oleg Isonen 77cb5403a3 fix: Allow publishing projects with custom elements (#5955)
## Summary

Prevent the pre-publish HTML content-model audit from crashing when a
project contains a tag that is not present in the generated HTML
metadata.

Unknown tags on generic Element instances now produce a non-blocking
publish warning. Legacy component instances remain permitted for
backward compatibility. Known HTML elements continue to use their
existing category and child constraints.

## Root cause

The content-model validator indexed `elementsByTag` and immediately read
`categories` or `children`. A project tag absent from that dataset
returned `undefined`, causing the publishing audit to throw before
publishing could continue.

## Impact

Projects containing unsupported tags can complete the pre-publish audit
instead of failing with an uncaught `categories` error. Invalid generic
element tags are still reported, and legacy components are not rejected
solely because their rendered tag is absent from current HTML metadata.

## Todo

- [x] Safely resolve HTML content-model metadata
- [x] Preserve validation for known HTML elements
- [x] Warn for unknown generic Element tags
- [x] Preserve legacy component compatibility
- [x] Add pre-publish audit regressions
- [x] Run project-build and builder formatting, lint, typecheck, and
tests

## Verification

- `pnpm exec oxfmt --check
packages/project-build/src/runtime/content-model.ts
packages/project-build/src/runtime/pre-publish-audit.test.tsx`
- `pnpm exec oxlint --deny-warnings
packages/project-build/src/runtime/content-model.ts
packages/project-build/src/runtime/pre-publish-audit.test.tsx`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm --filter @webstudio-is/project-build test` — 81 files, 2,021
tests passed
- `pnpm --filter @webstudio-is/builder test` — 189 files, 2,356 tests
passed
2026-07-29 15:58:39 +02:00
Oleg Isonen 0b192c7802 fix: Sort style sources with temporary local source (#5949)
## Summary

- Allow style sources to be reordered when the displayed local source
has not been persisted yet.
- Submit only attached style source IDs to the runtime reorder
operation.
- Add regression coverage for reordering tokens alongside a temporary
local source.

## Root cause

The style panel displays a temporary local style source until local
styles are persisted. Sorting included that temporary ID in the runtime
mutation, but the runtime accepts only attached style source IDs and
rejected the entire reorder.

## Todo

- [x] Filter temporary style source IDs from reorder mutations.
- [x] Add regression coverage.
- [x] Run targeted tests, lint, formatting, and Builder typecheck.

## Verification

- `pnpm --filter @webstudio-is/builder test --
app/builder/features/style-panel/style-source-section.test.ts` — 12
tests passed
- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/features/style-panel/style-source-section.tsx
apps/builder/app/builder/features/style-panel/style-source-section.test.ts`
- `pnpm exec oxfmt --check
apps/builder/app/builder/features/style-panel/style-source-section.tsx
apps/builder/app/builder/features/style-panel/style-source-section.test.ts`
- `pnpm --filter @webstudio-is/builder typecheck`
0.282.1
2026-07-24 15:45:48 +02:00
Oleg Isonen 5141f61f75 fix: prevent mixed router dependencies in MCP previews (#5948)
## Summary

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

## Root cause

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

## User impact

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

## Verification

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

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

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

## Measured baseline

Repository fixture:

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

Production homepage, read-only:

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

Editable local production-homepage clone:

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

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

## Correctness finding

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

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

## Proposed direction

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

## Performance target

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

## Verification

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

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

## Description

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

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

## Steps for reproduction

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

## Testing

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

## Code Review

@kof, could you please:

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

## Before requesting a review

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

---------

Co-authored-by: Sviat Barbutsa <9897782+sviat-barbutsa@users.noreply.github.com>
Co-authored-by: Oleg Isonen <oleg008@gmail.com>
2026-07-23 00:11:16 +02:00
Oleg Isonen c3c0e32204 fix: make MCP font assets editable and deterministic (#5944)
Closes #5943
Closes #5942

## User impact

MCP-managed font assets now keep their identity, metadata, and generated
CSS behavior predictable across upload, editing, synchronization, and
rendering.

## Changes

- Preserve explicit font metadata overrides while validating supported
fields and rejecting unsupported metadata instead of silently dropping
it.
- Expose font metadata through `update-asset`, with partial updates
merged into the existing metadata and validated before the mutation is
committed.
- Prefer the typographic family name when reading font files, so
Fontsource and Google font files resolve to the same logical family
where the file provides that information.
- Deduplicate generated font sources by URL and format, choose a
deterministic source when legacy assets collide, and derive the CSS
format hint from the filename when historical metadata is stale.
- Add MCP and CLI examples explaining font metadata upload and
correction.
- Add regression coverage for metadata persistence, invalid overrides,
family-name extraction, duplicate source handling, stale format
metadata, and generated runtime contracts.

## Verification

- `pnpm checks`
- 46 workspace packages tested, including 2,355 Builder tests, 809 CLI
tests, 2,019 project-build tests, and 97 asset-uploader tests.
- All workspace typechecks, Oxlint, package-boundary checks, generated
API/docs checks, and fixture link/sync/build checks passed.
2026-07-22 19:04:53 +02:00
Oleg Isonen a803aa352d fix: expand CSS shorthands in MCP style updates (#5941)
Closes #5940

## What changed

- Route shorthand properties from `update-styles`,
`update-design-token-styles`, and shared style mutation helpers through
the existing CSS parser.
- Store expanded longhand declarations so generated CSS and Builder
editing use the same data model as JSX fragment insertion.
- Preserve breakpoint, pseudo-state, and listed metadata across
generated declarations.

## User impact

MCP-authored shorthands such as `inset` and `grid-area` no longer
disappear from generated CSS or remain as declarations the Builder
cannot edit. The three style authoring paths now produce consistent,
editable project data.

## Verification

- `pnpm --filter @webstudio-is/project-build test` (2,015 tests)
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm oxfmt --check packages/project-build/src/runtime/styles.ts
packages/project-build/src/runtime/styles.test.ts`
- `pnpm oxlint --deny-warnings
packages/project-build/src/runtime/styles.ts
packages/project-build/src/runtime/styles.test.ts`
- `git diff --check`
2026-07-21 18:38:37 +02:00
Oleg Isonen 7eb58664b0 fix: harden asset folder uploads and selection (#5939)
## Summary

Fixes folder uploads and asset-manager selection behavior.

## Changes

- Preserve folder IDs for newly uploaded assets.
- Place duplicate assets into the dropped folder, including server-side
deduplication and assets already uploading.
- Handle browser `NotFoundError` cases while reading dropped
directories.
- Keep lasso selection anchored correctly while scrolling.
- Add `Cmd/Ctrl+A` to select all currently rendered assets and folders.
- Document the new shortcut in the keyboard shortcuts dialog.
- TIFF support was removed in the base branch.

## Verification

- [x] Asset upload and directory-drop tests
- [x] Asset-manager tests
- [x] Keyboard shortcuts dialog tests
- [x] Builder typecheck
- [x] Formatting and diff checks
2026-07-21 16:35:04 +02:00
Oleg Isonen 085f705cc4 fix: remove unsupported TIFF assets (#5938)
## Summary

- Remove `.tif` and `.tiff` from supported asset extensions.
- Stop categorizing TIFF files as images in the asset manager.
- Update SDK URL coverage to reflect the supported direct-served image
formats.

## Why

The asset manager classified TIFF files as images, but the browser and
image optimization pipeline cannot reliably render TIFF thumbnails.
Treating TIFF as supported created broken previews.

## Verification

- SDK asset tests: 95 passed
- Formatting completed
- Diff checked against `main`
2026-07-21 15:44:14 +02:00
Oleg Isonen 54aa7be145 fix: Make MCP authoring reliable across assets, styles, and embeds (#5932)
Closes #5924
Closes #5925
Closes #5926
Closes #5927
Closes #5928
Closes #5929

## Outcome

Makes MCP and CLI authoring predictable across project-wide CSS
variables, font uploads, retried asset uploads, destructive asset
selection, design-token collisions, and SVG HtmlEmbed edits.

## Delivered

### Project-global CSS variables

- [x] `define-css-variable` writes to `ROOT_INSTANCE_ID` / `:root`
instead of the home-page Body.
- [x] Breakpoint and overwrite behavior is preserved, and compact
results identify the root scope.
- [x] Existing body-local declarations remain untouched.
- [x] Runtime and Builder integration tests cover root selection and
generated project behavior.

### Font metadata and upload overrides

- [x] Static fonts prefer authoritative `OS/2.usWeightClass`.
- [x] Fallback matching normalizes spaces, hyphens, and underscores and
checks specific aliases before shorter ones.
- [x] Explicit format and partial metadata overrides travel through
HTTP, filesystem, and S3 upload paths and are validated after merging
with detected metadata.
- [x] Regression tests cover ExtraBold, Extra-Bold, Semi Bold Italic,
Ultra Light, valid weight classes, invalid weight classes, partial
overrides, and invalid overrides.

### Idempotent asset uploads

- [x] API uploads compute a project-scoped SHA-256 content hash.
- [x] Sequential and concurrent duplicate uploads reuse the existing
asset and return `deduplicated: true`.
- [x] `force: true` bypasses deduplication for intentional duplicates.
- [x] A partial unique index enforces one non-null content hash per
uploader project.
- [x] Stale reservations are cleaned up and active concurrent uploads
are bounded by polling and an actionable retry error.
- [x] HTTP, protocol, uploader, and CLI tests cover hash reuse,
uniqueness races, force propagation, and result markers.

The migration does not backfill historical files. Existing assets begin
participating in deduplication when uploaded through the new path;
matching content keeps the existing asset's metadata rather than
silently rewriting it.

### Consistent asset inputs and permissions output

- [x] `delete-asset` exposes exact `assetIds` and explicit
`assetIdPrefixes`.
- [x] The legacy `assetIdsOrPrefixes` input remains accepted and is
marked deprecated.
- [x] Exact ids no longer accidentally select longer prefixed ids.
- [x] `webstudio permissions` prints a readable role/capability summary
by default while `--json` remains unchanged for machine callers.
- [x] Help and discovery describe JSON output as optional.
- [x] Runtime, CLI, metadata, and generated-contract tests cover
canonical and compatibility behavior.

### Deterministic fragment design tokens

- [x] Fragment insertion reconciles incoming tokens against project
tokens and earlier tokens in the same fragment.
- [x] Equal name/style tokens are reused instead of creating suffixed
twins.
- [x] Same-name/different-style fragments require an explicit `ours`,
`theirs`, or `merge` policy at the public runtime boundary.
- [x] Builder's established internal paste behavior retains its explicit
incoming-value default.
- [x] `create-design-token` accepts documented CSS strings through the
property-aware CSS parser as well as typed style values.
- [x] Tests cover same-fragment reuse/conflicts, persisted tokens,
explicit policies, and CSS string parsing.

### Editable SVG HtmlEmbed content

- [x] HtmlEmbed validation uses parse5 parser errors and source
structure instead of serialization-string equality.
- [x] Valid inline SVG is accepted without silently normalizing authored
markup.
- [x] Fragment insertion now uses the same validator as prop update and
text replacement paths.
- [x] Malformed markup is rejected before mutation.
- [x] Regression tests cover realistic SVG, parser failures, and
no-partial-mutation behavior.

## Verification

- [x] `pnpm checks`: all workspace tests, typechecks, lint, package
boundaries, generated API/docs checks, and fixtures passed on the final
committed tree.
- [x] `pnpm --filter webstudio test:release-smoke`: packaged install,
compact comparison, preview, agent workflow, stdio MCP, and structured
commands passed.
- [x] Prisma schema validation passed with test connection variables.
- [x] Two minimally prompted weak-model subagents independently
exercised the changed CLI/MCP discovery and focused workflows.
- [x] The subagent-found stale `permissions --help` requirement was
fixed, regression-tested, and retested.
- [x] Final diff and worktree are clean.
2026-07-21 14:36:54 +02:00
Oleg Isonen 918e569fd4 fix: Show publish warnings immediately (#5935)
## Summary

Show client-side pre-publish audit findings as soon as the user starts
publishing, and apply the Builder's standard text styling to warning
banners.

## Root cause

The cloud publish audit ran inside an async form action, while the
static export audit ran inside an async transition. React deferred their
ordinary state updates until those async operations completed, so
warnings could appear only after publishing or export finished even
though validation itself ran synchronously on the client.

## Changes

- Run the cloud publish audit in the synchronous button click handler
before starting the async publish transition.
- Run the static export audit before starting its async transition.
- Keep warnings non-blocking while preserving blocking audit errors.
- Render warning-banner content with the design-system `Text` component.

## Checklist

- [x] Show cloud publish warnings before publishing starts
- [x] Show static export warnings before export starts
- [x] Style warning text consistently
- [x] Run Builder tests
- [x] Run Builder typecheck
- [x] Run lint and formatting checks

## Verification

- `pnpm --filter @webstudio-is/builder test` — 2,351 tests passed
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/features/publish/publish.tsx`
- `pnpm exec oxfmt
apps/builder/app/builder/features/publish/publish.tsx`
2026-07-21 11:57:00 +02:00
Oleg Isonen f08cc01c9f fix: Preserve tokens when pasting Slot content (#5934)
## Summary

Preserve design-token selections on instances nested inside pasted Slot
content.

## Root cause

Token style sources receive new or reused IDs during fragment insertion.
Regular copied instances applied that token ID mapping to their
style-source selections, but preserved Slot/portal content copied
selections with the stale clipboard IDs. As a result, an Accordion's
shared HtmlEmbed icon could lose its visible token after pasting the
whole Accordion even though pasting the icon alone worked.

## Changes

- Pass the token style-source ID map into Slot/portal style insertion.
- Remap token IDs while preserving local style-source IDs.
- Add unit coverage for portal selection remapping.
- Add a fragment-level regression test using a tokenized HtmlEmbed
inside a Slot.

## Checklist

- [x] Remap token selections in pasted Slot content
- [x] Add regression coverage
- [x] Run project-build tests
- [x] Run project-build typecheck
- [x] Run lint and formatting checks

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 1,993 tests passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm exec oxlint --deny-warnings
packages/project-build/src/runtime/style-copy.ts
packages/project-build/src/runtime/fragment.ts
packages/project-build/src/runtime/style-copy.test.ts
packages/project-build/src/runtime/fragment.test.tsx`
- `pnpm exec oxfmt packages/project-build/src/runtime/style-copy.ts
packages/project-build/src/runtime/fragment.ts
packages/project-build/src/runtime/style-copy.test.ts
packages/project-build/src/runtime/fragment.test.tsx`
2026-07-21 11:49:24 +02:00