84 Commits
Author SHA1 Message Date
Oleg Isonen 7cab28b633 fix: Allow Form Resources to reach sibling API hosts (#6623)
A Form on `site.example.com` can now send a Resource request to
`api.example.com`. Cloudflare fetches use public routing, while requests
back into the same app are stopped even when they arrive through a
different hostname.

## Checklist

- [x] Enable public `fetch()` routing in the self-hosted Cloudflare
Worker and Pages templates.
- [x] Narrow Cloudflare Form Resource hostname checks to site hostnames,
including published aliases; keep the Node transport policy intact.
- [x] Explain the routing and recursion boundary in code comments.
- [x] Add focused regression tests and verify they fail before the fix
and pass afterward.
- [x] Reject Cloudflare Resource requests that re-enter the app through
an alias, and verify both Worker and Pages paths.
- [x] Run relevant package tests, typechecks, lint, and fixture builds.
- [x] Review the authoritative `docs/` content. No user guidance
currently describes this hostname policy, so no docs change is needed.

The `global_fetch_strictly_public` flag affects all Cloudflare `fetch()`
calls in the generated app. It routes same-zone requests through the
public front door. The known site hostnames are blocked before fetch; a
reserved request marker carrying the originating project ID stops direct
reentry through any additional self-hosted alias while allowing other
projects. The marker is set on every redirect hop.

Verification: SDK suite (914 tests), CLI prebuild suite (143 tests), SDK
and CLI typechecks, published types, lint, package boundaries,
formatting, full fixture pipeline, and both Cloudflare fixture
typechecks/builds passed. The new marker test failed before the fix and
passed afterward. Local Pages returned 508 for a marked request and 200
for an ordinary request. Local Worker returned 508 for a marked request;
an ordinary local request hit a fixture-only `assets/process` module
error. The optional local fixture-size check lacked the unrelated React
Router Netlify built client artifact; the prior PR CI fixture-size check
passed.

Staging test: moved from #6621 to `frm.staging`. The Builder deployed at
https://frm.staging.webstudio.is/ and the Cloudflare template artifact
build and validation passed.

- [x] Publish the test site from staging and verify a Resource call to a
sibling API hostname, plus recursive-call protection.

Live staging verification: Published `ee-trpc-test-ivan.wstd.work` from
Builder commit `35d22fc8a`. A temporary Resource to `api.wstd.work`
produced that host's HTTP 401 response in published page data,
confirming the request reached the sibling host. A marked request
initially returned 200 because the SaaS publish Worker uses a separate
entrypoint. The matching guard is in webstudio-is/webstudio-saas#673;
deploying that branch made the marked request return 508 while an
ordinary request returned 200. The temporary Resource and binding were
removed and the clean build was republished with the SaaS fix.

## Resource response limit follow-up

- [x] Enforce 1 MiB (1,048,576 decoded bytes) for all HTTP Resource
responses, including page Resources, dependencies, Form actions, and
cache hits; reject before cache writes.
- [x] Cancel oversized responses and report `RESPONSE_TOO_LARGE` without
retrying. Show: “Resource response exceeds 1 MiB. Request fewer fields
or use pagination.”
- [x] Verify exact boundaries, streamed overflow, compressed responses,
cache behavior, and Form/loader error propagation with regression tests.
- [x] Update Resource and Form documentation to explain the limit and
recovery.
- [x] Monitor CI after pushing the verified implementation. All checks
passed for `f59b770d45112581514eaa8bc42f57b7f27d290b`, including all ten
browser E2E jobs, visual regression, Linux/Windows fixtures, and staging
builds/deployments.

Verified locally: all 926 SDK tests, SDK typecheck and published types,
lint, package boundaries, Form browser boundary, formatting, full
fixture pipeline, and Cloudflare React Router fixture typecheck/build
passed. All 12 new tests and the updated transport test failed against
the previous implementation; disabling only the retry guard also
reproduced a duplicate attempt. A local Cloudflare runtime test with
gzip accepted exactly 1 MiB of decoded data and rejected one byte over.
The full fixture sync pulled an unrelated saved-project text change; the
original fixture source was restored and regenerated, leaving no fixture
diff.

The response limit is enforced before JSON parsing and before Resource
cache writes, and checked again when loading cache hits. Oversized
responses return `RESPONSE_TOO_LARGE` with `retryable: false`; the graph
honors that flag. System and Email Resources retain their own limits.
The implementation adds 59 net non-test code lines plus 5 documentation
lines, below the 70–100 estimate. Browser E2E passed in CI. Agent
evaluations were not run; they require separate permission and were not
needed for this transport change.


## Simplification follow-up

- [x] Reuse Content Engine's bounded byte reader for Resource bodies and
Form uploads; remove duplicate accumulation and cancellation loops
(estimate: about 40 fewer production lines).
- [x] Preserve response limits, request limits, cancellation, error
messages, and retry behavior; run existing SDK and Content Engine
coverage plus package/build checks.
- [x] Review documentation impact; behavior and approved copy should
remain unchanged.
- [x] Push the simplification and verify CI. All checks passed for
`755ec4210a7858fe298ad4d3a725a0167fb5bb26`, including all ten browser
E2E jobs and staging deployment checks.

The simplification removes 37 net non-test lines, reducing the
response-limit implementation from 59 added lines to 22. Content Engine
now exposes its existing byte-stream module directly and accepts a
caller-provided limit error. Resource bodies and Form uploads reuse that
reader. Native Response construction also replaces redundant
status/header copying. All existing tests remain.

Verification: 926 SDK tests and 655 Content Engine tests passed, along
with both packages' typechecks/builds, published types, lint, package
boundaries, Form browser boundary, formatting, the full fixture
pipeline, and a Cloudflare fixture build. Local Cloudflare runtime
checks passed for gzip at/over the limit and empty 204/205/304
responses. The generated fixture vendor change is limited to the shared
reader's optional error argument. Documentation and approved copy remain
accurate and unchanged.

## Response handling cleanup

- [x] Share bounded response reconstruction between protected fetches
and the cache; remove stale encoding/length headers after decoding
(estimate: 5–10 fewer non-test lines).
- [x] Reproduce the protected-response header bug, then verify the fix,
limits, empty responses, and existing SDK coverage.
- [x] Review documentation and comments for accurate scope; preserve
approved user-facing copy.
- [x] Run relevant checks, regenerate fixtures, push, and monitor CI.

The shared response reader fixes stale compression and length headers in
protected fetches and removes four net production lines (slightly below
the 5–10 estimate). All 927 SDK tests, types, published types, lint,
package boundaries, and Form browser boundary passed. The new regression
test failed on the old code and passed after the fix. Cloudflare runtime
checks passed for decoded bodies and empty 204/205/304 responses. The
existing Resource/Form documentation remains accurate; approved copy is
unchanged.

The reentry marker now carries the originating project ID. A different
project can serve the request; the same project rejects direct reentry
through any hostname. This is cooperative loop detection, not a sandbox
for arbitrary code.

- [x] Reproduce and fix overflow and redirect hangs when a response has
an unread clone; preserve stream cancellation and error handling for all
shared-reader callers.

The clone regressions failed before the fixes and passed afterward.
Cancellation still starts immediately and releases the reader lock, but
no longer waits for an unread tee branch. Both overflow rejection and
redirect following are covered. All 929 SDK tests and 655 Content Engine
tests passed, along with both typechecks and lint. A local Cloudflare
runtime also rejected a cloned oversized response promptly. Combined
with response reconstruction reuse, this cleanup removes five net
production lines. Fixture regeneration changes only the bundled
cancellation logic.

Final verification for `7761f67d6d1e0a853a6ef239f482ebaf9d70c1ae`: all
CI checks passed, including all ten browser E2E shards, visual
regression, package/Builder/CLI tests, size checks, Linux and Windows
fixtures, and staging deployment workflows. Full fixture regeneration
and the Cloudflare fixture build also passed locally. No agent
evaluations were run; they require separate permission and were not
needed for these fixes.

## Scope reentry protection to the project

- [x] Replace the global marker with the originating project ID and
compare it with generated project metadata before handling requests,
including cached requests.
- [x] Cover unrelated sites, same-project aliases, and redirects with
regression tests; demonstrate failure before the fix.
- [x] Review documentation and clarify that this is cooperative loop
detection, not a sandbox for untrusted code.
- [x] Regenerate fixtures, run relevant checks, push, and monitor CI.
Estimate: 20–30 implementation lines across both repositories, plus
generated metadata and tests.

Implementation: reuse the project ID already exported by the generated
home-page module; no new metadata file or package. The generated
Cloudflare Resource adapter passes the same ID to the SDK factory, which
now requires it. Guards compare that ID before rendering or cache
lookup. SDK and generated-adapter regression tests cover another project
returning 200 and an unknown same-project alias returning 508. Hosted
Worker tests verify the guard runs before the cache and permits another
project's cached response. The original guard and a blanket-guard
mutation fail the regressions. A local Cloudflare runtime reproduced the
expected 200/508 behavior. No user-facing copy or documentation changes
are needed; code comments state the cooperative boundary. The fix adds
19 implementation lines across both repos, below the 20–30 estimate,
plus tests/configuration and regenerated fixtures. Existing deployments
require republishing with the updated sender and guard to use
project-specific markers.

Final project-specific reentry verification: 930 SDK tests, 143 CLI
prebuild tests, and 41 hosted-template tests passed. SDK/CLI types,
published SDK types, lint, package boundaries, full fixture
regeneration, both Cloudflare fixture builds, and the Pages function
bundle passed. The actual Pages function returned 200 for another
project's marker and 508 for its own marker through an unknown alias in
a local Cloudflare runtime. All CI checks passed for Builder
`433f9a359d469ca3669e383c4516c3e805ea22a1`, including all ten E2E shards
and staging workflows; companion SaaS
`3e28e7e33813dd968f33caac35dbbba52a3847d3` checks and preview deployment
also passed. Agent evaluations were not run because they require
separate approval; no evaluation changes were needed.
2026-10-30 22:42:27 +01:00
Oleg Isonen 283929bb49 fix: Make Form progress text editable (#6617)
A new Form’s Progress message shows “Submitting…”, but Builder cannot
edit that text because it sits directly beside the nested byte summary.
The template now gives the message its own Span. Adding an element to an
existing text container also wraps its existing text, using the same
guard for Builder and MCP insertion and Builder moves.

Closes #6616
Ref #6605
Ref #5992

## Plan

- [x] Reproduce the new Form structure through template, Builder, and
MCP insertion.
- [x] Give the submission text its own editable element while preserving
the bound summary.
- [x] Add Form regression tests, update docs, and verify the first
change.
- [x] Reproduce insertion into an existing text container through the
shared runtime and MCP.
- [x] Share destination-parent normalization across insertion and move,
preserving authored fragment internals and replace behavior.
- [x] Add regression coverage for editable text, rich text, undo, and
insertion results.
- [x] Review documentation impact of the shared guard.
- [x] Run targeted tests, typecheck, lint, and applicable broader
checks.
- [x] Confirm required CI for the final commit.

## Scope and compatibility

The guard acts on existing content in the destination parent when
inserting an element. It does not rewrite descendants inside the
inserted fragment, so intentionally mixed rich text remains as authored.
Indexed replacement wraps surviving text; full replacement leaves no old
wrappers. Generated spans and the parent rewrite are committed together,
so undo and redo restore the original structure. Existing saved Forms
keep their structure; no migration is included. The submission wording
and bound byte summary stay the same.

The shared runtime change adds 88 net non-test lines, above the 45–65
line estimate. The extra lines cover the two position formats used by
moves and insertions, atomic parent patching, indexed replacement,
generated ID checks, and insertion results. No schema, package, or
production data changes.

## Verification

Local: project-build 2,667 tests passed; Builder insertion 45 tests
passed; project-build and Builder typechecks passed; targeted oxlint,
oxfmt, and git diff checks passed. An independent Astra review found no
blocking defects. Agent evaluations require separate approval and were
not run. Required CI for the final commit passed: 35 successful checks
and one skipped check. Builder end-to-end shard 8 failed once during
generated Preview submission with status “unknown”; its targeted rerun
passed without source changes.
2026-10-28 19:26:29 +01:00
Oleg Isonen c9f6435aba fix: Allow ECMAScript module asset uploads (#6613)
A `.mjs` file currently fails to upload with HTTP 500, so the CLI
retries the same unsupported format and reports a retry-exhausted error.
This PR lets people upload ECMAScript modules as JavaScript assets.
Files with unsupported extensions now get HTTP 400 and the CLI does not
retry them.

Closes #6612

The existing asset-type registry now includes `.mjs` with the same MIME
type and editor as `.js`. Filename validation uses a typed error so the
Assets API can return 400 without inspecting error text. Transient
server failures still retain the existing retry. The upload still
transfers the file and checks its size and content; this change does not
remove those costs.

- [x] Reproduce the `.mjs` rejection locally and show new tests fail
before the fix.
- [x] Accept `.mjs` in upload detection, serving MIME lookup, and the
Assets panel Code filter.
- [x] Return HTTP 400 for unsupported extensions.
- [x] Stop the CLI after a non-retryable upload response. The regression
test failed with two requests before the fix and passes with one
afterward; HTTP 500 still retries.
- [x] Review documentation impact and add `.mjs` to the authoritative
Assets guide. The retry behavior needs no further docs change.
- [x] Verify fixture impact: no fixture asset has format `mjs`; no
generated fixture input or output changes.
- [x] Run focused tests (SDK 133, asset uploader 8, Builder Assets API
23), then full SDK (910) and asset-uploader (313) suites.
- [x] Run SDK, asset-uploader, Builder, project-build, and HTTP-client
typechecks; lint, package-boundary check, formatting, and diff check.
- [x] Verify required CI checks after the retry fix: main workflow
(including all ten Builder E2E shards), visual regression, staging
build, lint, and submodule checks passed.

The reporter’s exact package file was not available. The regression uses
a small valid `.mjs` file and verifies the same upload-validation path.
The full agent evaluation suite was not run, as requested; focused tests
were used instead.
2026-10-27 14:32:28 +01:00
Oleg Isonen 9a8ae2dc8f feat: Customize Form progress feedback (#6606)
Closes #6605

New Forms show editable feedback while Resource actions run. The
`formProgress` variable starts with `status: "idle"`, `loaded: 0`,
`total: 0`, and `files: []`. It becomes pending during submission, then
returns to idle. The existing `formState` still reports only initial,
success, or error.

The Progress container says “Submitting…” and adds measured
whole-request bytes when the total is known. Files include ID, field
name, name, size, and type. The browser cannot measure reliable per-file
transfer bytes in the single multipart request. The binding editor
suggests one example file to reveal those fields; the example never
enters the actual Form value. Saved Forms retain their existing bindings
and submission message.

## Checklist

- [x] Keep the existing Form outcome variable and add upload activity,
byte counts, and file details.
- [x] Show zero byte defaults on insertion, new submissions,
cancellation, and Preview reset without fake runtime files.
- [x] Suggest file fields in Form and Resource bindings while
evaluation, reset, rendering, and stored values use real data.
- [x] Preserve Builder Preview headers, authentication, response checks,
and cancellation.
- [x] Keep existing success/error feedback and saved-Form fallback.
- [x] Reset external progress when an active published Form unmounts.
- [x] Update Form, Variables, and MCP/CLI guides; clarify the older
screenshot and final-only simulation. Generated MCP docs check passes.
- [x] Audit Form and Variables docs against final progress behavior;
clarify the older Variables screenshot.
- [x] Cover lifecycle, transport, repeated submission, cancellation,
unmount, Preview, template, MCP binding/style, and editor completion
with focused tests.
- [x] Run relevant typechecks, lint, formatting, fixture generation, and
independent review.
- [x] Required CI checks pass on the latest commit.

## Verification

Local focused Form browser tests (25), Form template tests (12), Builder
Preview and Form context tests (14), and mounted editor example/reset
tests (6) pass. Builder, SDK React, SDK registry, and project-build
typechecks pass, as do lint, formatting, and fixture generation. The
fixture sync regenerated two unrelated linked-project site paths; those
outputs were restored before commit. Independent review found no
remaining issues. All latest-commit checks passed; E2E shard 9 passed on
a targeted rerun after its two Form tests also passed locally with 11
related tests.

This follow-up adds 162 net non-test lines, more than the initial
estimate. The extra code identifies the actual callback-bound variable
by ID, even when renamed, and gives autocomplete a separate example
scope so it cannot affect actual values. One provider covers Form fields
and Resource editors. No schema or package was added.

The existing Variables screenshot predates `formProgress`; its
explanatory text is current. The React Router fixture's built browser
client grew by 4 KiB on Linux CI (376 → 380 KiB), and its checked limit
matches that measured output. Multipart transfer remains one whole
request.

Browser submission supplies live progress. MCP can discover
`formProgress`, bind the Progress container, and style it; server
simulation reports final outcomes and does not measure browser uploads.
2026-10-26 14:38:12 +01:00
Oleg Isonen 659e953869 fix: Preserve transitions and animation preview behavior (#6602)
Transition variables no longer get copied into unrelated timing fields.
Generated Text/Stagger animations use the saved `slidingWindow` prop,
while older `charWindow` configurations still work. Easing moves in the
expected direction. Windows preview-install reports include the process
failure code when available.

Closes #6598
Ref #6593
Ref #6599
Ref #6594

The animation runtime lives in [sdk-components-animation
#38](https://github.com/webstudio-is/sdk-components-animation/pull/38).
The private PR is merged; this PR points to its merged commit. #6593 was
tested on its exact saved project in headless Chromium and closed as not
reproducible; the confirmed prop leak is fixed here. #6594 was closed
after a documented local repro attempt; this PR improves the next
report.

## Work

- [x] Reproduce the transition bug; add regression coverage in parsing
and generated CSS.
- [x] Preserve unresolved `transition` shorthands for the browser to
resolve.
- [x] Keep old `charWindow` values working in Text and Stagger
animations, with a red/green regression test in the linked private PR.
- [x] Reproduce and fix the saved animation prop mismatch in generated
helpers.
- [x] Verify text/stagger easing in unit tests and Chromium/Firefox.
- [x] Add safe preview-install process details for future Windows
reports.
- [x] Update Builder property help and both animation docs pages.
- [x] Run focused animation tests, typecheck, lint, package build, and
animation fixture build after the compatibility fix.
- [x] Point the animation submodule to the merged #38 commit.
- [x] Verify required CI on the final commit (all workflows passed; one
unrelated E2E shard passed on rerun).
- [x] Find the production share token and build #6593’s exact project
with its reported 0.307.0 packages.
- [x] Compare intermediate scroll styles on #6593’s exact project.
Visible targets animate in both builds; the leaked attributes disappear
with this fix.

Known limit: the reported static-target symptom in #6593 and the Windows
environment in #6594 were not reproduced. The confirmed animation prop
leak and reporting gap are fixed, without claiming the unreproduced
symptoms are solved.
2026-10-25 19:12:25 +01:00
Oleg Isonen f1c3abf1be docs: Refresh accessible Builder screenshots at 2x (#6595)
## Outcome

Refresh the accessible low-resolution Builder documentation screenshots
with current, native 2× light and dark captures. Images retain useful
crops, descriptive alt text, and explicit display widths.

The final audit accounts for 165 candidate files: 132 refreshed, 29
inaccessible screenshots kept with the user's approval, two existing
images made 2× by setting their display width, and two Content Engine
query screenshots held because the current Builder shows a false
draft-filter type warning. Those two originals remain in place pending a
separate Builder fix and recapture. Four animated GIFs remain outside
this still-screenshot refresh.

## Review checklist

- [x] Inventory image references, native dimensions, display widths, UI
states, and source pages; audit omitted references across all docs.
- [x] Replace the approved Form pairs and accessible Builder screenshots
with matching 2× light and dark captures.
- [x] Keep inaccessible external/account/desktop and disabled Export
images, as approved.
- [x] Review size-sufficient images and GIF/diagram exclusions.
- [x] Review crops, alt text, widths, links, and source references.
Independent screenshot review passed.
- [x] Check the full docs image inventory: 322 placements, 284 unique
assets, zero missing files. The only unaccounted sub-2× refs are four
excluded GIFs.
- [x] Run `git diff --check` and verify all 204 changed image files are
referenced; 117 changed picture placements have matching themes and at
least 2× native width.
- [x] Verify required PR checks: Lint PR, Check submodules, Visual
regression, and Main workflow all passed at 14937127db.
- [ ] Recapture the two Content Engine query images after [Builder fix
#6597](https://github.com/webstudio-is/webstudio/pull/6597) reaches the
Builder used for capture. Keep their originals until then.\n- [ ]
Confirm GitBook rendering at intended CSS widths after publication. This
repository has no PR preview.

## Held images

`content-engine-overview-query.png` and
`content-engine-article-query.png` are unchanged. The current Builder's
query form reports a false type warning for the Boolean
`properties.draft` filter. Screenshotting that state would teach a
broken workflow. The user approved a separate [Builder query type fix in
PR #6597](https://github.com/webstudio-is/webstudio/pull/6597). Once
that fix is reviewed, the two screenshots can be recaptured from the
corrected UI. This PR does not change application code.

All captures use synthetic content. No release or publication branch is
changed.
2026-10-25 10:11:58 +01:00
Oleg Isonen 74af992644 docs: Restore screenshots in the Form guide (#6592)
The Form guide has five screenshot gaps on the published docs page.
GitBook shows the new text, but renders the screenshot blocks as empty
paragraphs.

This changes those blocks to the `figure > picture` markup used by
working docs pages. It also removes width attributes that are
unnecessary because the JPEGs already have the intended dimensions. The
light and dark images and their alt text stay the same.

Follow-up to #6591.

## Checklist

- [x] Compare the broken Form markup with working docs pages.
- [x] Update all five screenshot blocks.
- [x] Review documentation impact: only the Form guide needs a change.
- [x] Verify all ten image paths, dark-mode sources, alt text, and the
diff.
- [x] Get an independent review of the screenshot change.
- [x] Correct Form limits guidance using production Form and email
Worker evidence.
- [x] Diagnose and fix Builder E2E shard 5 form submission failure.
- [ ] Get an independent review of the follow-up changes.
- [ ] Confirm the screenshots render in GitBook after publication.

## Verification

- `git diff --check` passed for the screenshot and limits changes.
- Production probes confirmed the published 25 MiB boundary, Preview's
smaller Vercel ingress, the Worker's 7 MiB JSON boundary, and
approximate email rate limiting (56 accepted before four were limited).
- The 25 MiB outbound HTTP body check exists in code, but an end-to-end
request was blocked by an unrelated 502; the guide states it as
Webstudio's body-size check.
- A local HTML parser confirmed five figure/picture blocks and ten
existing assets.
- `oxfmt` does not check `docs/**`, which is excluded by repository
configuration.

GitBook rendering needs a published revision to confirm the fix. The
live page currently renders all five original picture blocks as empty
paragraphs; its text is current, so this is not a stale publication.

## CI status

The focused legacy Form query test passed locally (2 tests), and the
previously failing Builder E2E shard 5 passed on commit `5f4ee65bf`. The
test now allows 30 seconds for browser submission, matching the adjacent
managed Form test. Its success, HTTP response, payload, and delivery
assertions are unchanged. All other checks on that commit passed or were
still running when the docs copy correction was pushed.

CI for the latest commit `9934bc752` is running. The screenshot
rendering check still requires a published GitBook revision.
2026-10-24 16:13:27 +02:00
Oleg Isonen 0a4e3a48dd docs: Explain Form capabilities with focused screenshots (#6591)
## Summary

Explain what the Form component can do, with short chapters for actions,
email, uploads, search and filters, submission feedback, and Preview
testing.

- Replace the old guide with the reviewed text.
- Add five tightly cropped screenshots, each in light and dark themes.
- Update links from the Variables and Webhook Form guides to the new
sections.

## Checklist

- [x] Update the Form documentation and review its behavior claims
against the implementation.
- [x] Add five matching light/dark screenshot pairs with descriptive alt
text.
- [x] Verify 21 local links and anchors, all 10 image references,
formatting, and the final diff.
- [x] Fix the CI database port conflict in browser shard 2.
- [x] Re-run the shard 3 email tests locally with the CI build and
schema artifacts: 4/4 passed in 61 seconds.
- [x] Use sanitized CI diagnostics to investigate the MCP submit
failures, then remove all temporary diagnostic code.
- [x] Pin the MCP test fixture to installed Playwright Chromium and run
E2E shards with two workers.
- [x] Review documentation impact of CI changes: no additional docs are
needed.
- [x] Confirm required CI checks pass.

The CI follow-up keeps test setup stable: Docker assigns free PostgreSQL
and PostgREST host ports, the MCP test fixture uses the installed
Playwright Chromium, and each E2E shard uses two workers. Local startup
passed with port 55434 occupied; the focused email tests passed 4/4. The
final clean CI run passed all ten E2E shards, Main, Visual regression,
Lint PR, and Check submodules. Temporary diagnostics have been removed.
The exact cause of the earlier browser startup failures was not proven.
These CI changes do not change Form behavior.
2026-10-24 15:01:02 +02:00
Oleg Isonen dd99d27e5e fix: Stop returning unqueryable unpublish jobs (#6588)
Unpublish finishes in the request, but it returned a job ID that
`get-publish-job` could never find. It now returns the domains it
unpublished. A missing publish job also stays a normal not-found error
instead of suggesting a CLI update.

Closes #6578

## Changes
- Keep filename-based E2E shard discovery. Rebalance the files across
ten shards using measured CI times; each shard keeps its own test
environment and the same 12-minute safety timeout.
- Remove the fabricated unpublish job ID. `get-publish-job` remains for
publish jobs.
- Remove publish-only flags from the unpublish CLI and clarify CLI/MCP
guidance.
- Keep legacy optional server inputs for compatibility. The API input
contract does not change; clients that read the old `jobId` result need
to use `unpublishedDomains` instead.
- No database migration. Existing domain-removal behavior is unchanged,
including retention of other published domains.

## Checklist
- [x] Reproduced both reported failures with tests before the fix.
- [x] Reviewed the change for compatibility, redundant code, and
retained-domain behavior.
- [x] Updated the authoritative MCP docs and regenerated the CLI/MCP
docs.
- [x] Ran focused Builder, CLI, domain, and build tests; workspace
typecheck, lint, package-boundary, generated API, and generated-doc
checks.
- [x] Restore filename-based shard discovery and rebalance the files
across ten shards.
- [x] Verify every Chromium workflow test runs in exactly one shard and
update the E2E contributor docs. All 141 tests are assigned once across
the ten shards.
- [x] Verify parallel-safe long E2E files and the two-submission legacy
form test in CI.
- [x] Let independent slow E2E files run their tests concurrently; all
ten shard jobs passed.
- [ ] Confirm all E2E jobs finish in under five minutes. Eight did;
shard 1 took 5:10 and shard 4 took 6:32. Move the long Content-mode page
and shared-project bridge files to separate shards, then remeasure.
- [x] Required CI checks pass.

No live unpublish was run because it would change a published site.
Agent evaluations were not run; they require separate approval.
2026-10-23 18:59:44 +02:00
Oleg Isonen 75d710e759 fix: Let Editor links patch Content Block text and props (#6575)
Editor links with API access can edit text and editable props inside
Content Blocks through raw CLI/MCP patches. Builder and Viewer
permissions stay unchanged.

UI and API requests use the same Content-mode policy. The API keeps its
narrower text-and-props scope; page, asset, style, expression, and
structure changes remain blocked for raw Editor patches.

### Implementation

- Use the persistence patch engine for instance, prop, and style state
changes. Remove the separate instance patch interpreter and record
writes from permission validation.
- Check prop changes against the resulting state. Image dimensions work
even when their patches appear before the source patch.
- Move Image source, dimensions, and alt updates into the shared
runtime. Video source changes preserve its existing dimensions. The UI
no longer sends separate follow-up writes. Explicit overrides and
expression bindings are preserved.
- Separate capability discovery from transaction validation and move the
server permission adapter out of the router.
- Reject missing replacement targets and protected or renamed prop-ID
reuse. Keep template-copy protections.
- This refactor removes 19 non-test lines. It removes eager
whole-instance cloning, but the server still loads the project and
validates its content tree. Empty transactions skip that validation.

Closes #6573

### Checklist

- [x] Reproduce the Editor-token denial and add regression coverage
- [x] Keep Editor raw patches limited to text and editable props
- [x] Use the existing patch engine and test missing targets, immutable
input, and permission boundaries
- [x] Share media updates between UI and MCP; test atomic updates, patch
ordering, overrides, and bindings
- [x] Separate capability discovery, permission validation, and API
orchestration
- [x] Verify new regression tests fail with the relevant behavior
disabled, then pass after restoration
- [x] Run runtime tests, API/sync tests, typechecks, lint, formatter,
package-boundary, and generated-API checks
- [x] Run Content-mode E2E checks for templates, assets, page creation,
collaboration, and save/reload
- [x] Add a CLI MCP Editor-token save/reload E2E, including protected
template rejection
- [x] Return a structured forbidden error for rejected Editor MCP
content changes
- [x] Run the complete Luna/low evaluation suite; record failures below
- [x] Review documentation and fixture impact: no new API inputs, schema
changes, migrations, or fixture generation changes; existing guidance
remains applicable
- [x] Correct the project-package permission fixture to include its
owning instance and run all package tests
- [x] Cover repeated media source updates and bound namespace batches in
Editor requests
- [x] Preserve stale retry handling before Editor content validation,
with API regression coverage
- [x] Preserve expression-bound media metadata during source replacement
and test saved Content-mode output
- [x] Reuse batch merging so split prop payloads validate against their
final state; test split and combined forms
- [x] Push the refactor and verify required CI completes
- [ ] Deferred: reproduce the separate Builder UI report on a
session-free Editor link
- [ ] Deferred: confirm image-description editing expectations
separately

### Verification

- Full project-build suite: 2,597 tests passed. Focused permission/prop
run: 121 passed.
- All package tests (`pnpm test:packages`) passed after correcting the
project-package fixture. All 1,297 CLI tests passed.
- Builder API and sync authorization: 54 passed; settings-panel tests:
11 passed.
- Content-mode E2E: 9 passed, including setup, plus the earlier focused
Image replacement run.
- Builder/project-build typechecks, lint, formatting, package
boundaries, and generated-API checks passed. The CLI content-runtime
bundle test passed; no generated fixture output changed.
- Earlier full Luna/low evaluations: **7/9 passed**.
`managed-form-runtime-v1` failed `runtimeFormAction`; `markdown-blog-v1`
failed `blogRouteEvidence`. These checks also failed before this
refactor. MDX editing and Markdown-reference discovery passed. Baselines
for this model/effort were incompatible or missing and were not updated.
This suite is not fully green.

- Latest full Luna/low rerun after review fixes: **6/9 passed**. Form
runtime, blog authoring, and MDX editing failed; the MDX fixture passed
in the earlier run. Baselines were not updated. The evaluation suite
remains incomplete.

- Retry follow-up: all 59 API/router tests and 28 persistence patch
tests passed, along with Builder typecheck and lint. Older retries use
existing persistence handling; current and future versions remain
permission-checked.

- Bound-media follow-up: Image and Video source replacement preserves
expression-bound width/alt and still passes shared Content-mode
validation. Full runtime tests, typecheck, and lint passed after this
fix. The agent evaluation results above precede the final retry and
binding guards; those guards have dedicated regression coverage.

- Split-payload follow-up: reuse the existing batch merge helper before
permission validation. Combined and split media patches pass the same
final-state checks. Full runtime tests, 59 API tests, typechecks, and
lint passed. Agent evaluations were not repeated after this follow-up.

- CI passed on `1242ba8344`: all package, Builder, and CLI test jobs;
all six Builder E2E shards; visual regression; typecheck/lint;
generated-file checks; packaged SSG; size; migrations; and submodule
checks. Development deployment checks passed. All review threads are
resolved.

### Final review follow-up (2fbb30ffe6; CI passed)

- [x] Reject future base versions before saving; test an otherwise
allowed Editor patch
- [x] Preserve sequential patch behavior and test
authorization/persistence parity
- [x] Run focused tests, typechecks, lint, and review
documentation/fixture impact
- [x] Commit and push these follow-up fixes, then verify CI

Local follow-up verification: 2,600 project-build tests, 96 project
tests, and 60 API tests passed. Builder/project-build typechecks,
changed-file lint and formatting, package boundaries, and diff checks
passed. Regression tests failed without the fixes and passed with them.
The shared patch engine uses one Immer draft and supports namespace
replacement without mutating input. Net production change: 6 lines. No
documentation, fixture inputs/outputs, schemas, or generated contracts
changed. Browser E2E and agent evaluations were not rerun for this
follow-up; the race and patch-order cases are covered by deterministic
tests.


CI passed on `2fbb30ffe6`: all six Builder E2E shards,
package/CLI/Builder tests, typecheck/lint/generated checks, visual
regression, migrations, SSG build, size, and submodule checks. No new
blocking findings in the follow-up review. Agent evaluations were not
rerun; earlier limitations remain recorded above.

### Asset review follow-up

- [x] Reproduce and block asset-valued design-only props, retaining
matching Image metadata
- [x] Stop generating image metadata for Video source updates
- [x] Verify regression tests, documentation/fixture impact, and CI;
reply to both review threads

Asset follow-up `2ea28028c0`: shared metadata eligibility now checks
asset values and their matching Image source, including MDX
materialization. Video source edits do not generate Image-only metadata.
New and updated regression tests failed without the fixes and pass with
them. All 2,604 runtime tests, 60 API tests, 30 persistence tests, both
typechecks, lint, formatting, and package boundaries passed. No
documentation, schemas, or fixture data changes are needed. Agent
evaluations were not rerun. Both review threads have replies and are
resolved. CI passed on `2ea28028c0`, including all six E2E shards,
visual regression, package/CLI/Builder tests, typecheck/lint/generated
checks, migrations, SSG build, size, and submodules.

- [x] Reproduce repeated source updates without a saved source, coalesce
before ID assignment, and verify regression tests/CI

Final source follow-up `16c1c5f68e`: repeated instance/property updates
are coalesced before assigning IDs, replacing the later source-only
check. Both new Image/Video regression cases failed before the fix and
pass now. All 2,606 runtime tests, typecheck, and lint passed. CI passed
on this commit, including all six E2E shards and visual regression. No
documentation or fixture-data changes were needed; evaluations were not
rerun.


### Coverage audit

- [x] Audit changed behavior against assertions, including UI/API and
persistence boundaries
- [x] Add test assertions for identified coverage gaps; automatic CI
passed
- [x] Review documentation and fixture impact: no product documentation
change; one new SVG used only by browser E2E

Per request, no local tests or agent evaluations were run for this
audit. Automatic CI passed on `4591940169`, including package/API tests,
all six browser-test shards, visual regression, typechecks, lint, and
generated-file checks.

Coverage additions are pushed in `2371d9796a` and `4591940169`: check
actual text sent to persistence; duplicate ordinary props across
instances; rejected edits leaving capabilities reusable; merged patch
ordering and input immutability; accepted/rejected linked Image
dimensions in MDX templates; returned style/source/selection state; and
UI Image dimension changes followed by reload. The previous image E2E
used equal-sized files and could miss broken dimension synchronization.
Its new SVG has different dimensions. Production code is unchanged. No
tests or evaluations were run for this audit, as requested; no new pass
claim is made.

- [x] Inventory every changed/moved/removed production function and map
it to assertions
- [x] Add missing mixed-child and dependent-transaction coverage
- [x] Push inventory follow-up and inspect CI results

### Function-by-function coverage inventory

This inventories production functions changed, moved, or removed by this
PR. “Covered” means the named test asserts the behavior; it does not
claim every possible input or branch is covered. No local tests or
evaluations were run for this inventory.

| Function / handler | Assertions traced |
| --- | --- |
| `renderProperty` and its `onChange` callback | Content-mode props E2E
checks text, href, Image/Video source, alt, protected controls, and
reload. Asset E2E now changes dimensions from 120×80 to 240×160, then
replaces/reloads to 120×80. |
| `createContentModeCapabilitiesFromBuild` (moved unchanged) | API
“commits content-mode mutations with editor api tokens” and “limits
editor api patches to content instances and props” exercise conversion
from saved build data. |
| `assertContentOrBuildTransactions` | Editor vs Builder, design denial,
transaction/payload limits, all-or-nothing rejection; new exact
10-transaction boundary and dependent append-then-edit transactions. |
| `contentOrBuildMutation` | API semantic text edit asserts version,
authorization, actual saved text, unchanged outside content, and
unchanged input. |
| `build.patch` handler | API tests assert allowed text forms, denied
structure/styles/assets/expressions/outside roots, Viewer/no-API denial,
stale retries, current-version validation, and future-version CONFLICT
before persistence. |
| `getContentModePropNamesByTag` (moved unchanged) | Derives names by
tag and merges names for matching tags. |
| `getDefaultContentRootIds` (moved unchanged) | Default vs explicit
roots, Body selection, template exclusion. |
| `getContentModeFrontmatterBoundTargets` (moved unchanged) | Explicit
Body/frontmatter scope, read vs readwrite expressions, recursive
instances. |
| `getContentModeEditableInstanceIds` (moved unchanged) | Outside roots,
nested templates, missing references/roots, non-id children, cycles. |
| `getContentModeEditableInstanceRootIds` and its recursive `visit`
(moved unchanged) | Same traversal cases; transaction tests reject
moving content between roots and duplicate references. |
| `getInstancePropMetas` (moved unchanged) | Tag-derived props,
unknown/design-only/incompatible props, no generic HTML-attribute
permission bypass. |
| `getContentModeEditableStaticPropNames` (moved unchanged) | MDX
template tests assert exact editable names, primitive conversions, and
retained/ignored design-only props. |
| `getContentModePropEligibility` | Matching/missing/different asset
sources, protected asset props, exact primitive types, UI/transaction
parity, Image vs Video. |
| `isContentModeCopyableProp` (moved unchanged) | Editable/copyable
classification and content-mode copy-policy test. |
| `getContentModeCapabilities` | Editable IDs, roots, frontmatter
bindings, asset metadata; shared UI and transaction eligibility test. |
| `isContentModePropTransition` | Schema/id/owner/name checks, unknown
instance, protected existing props, permitted source type changes,
linked dimensions. |
| `validatePropPatches` | Add/replace/remove, new-then-update, missing
replacement target, protected ID reuse, copied template defaults,
final-state media patches in split/reversed order, failed-edit
immutability and retry. |
| `hasSameInstanceMetadata` | Whole-instance text replacement allowed;
tag/name changes and outside references rejected. |
| `validateStyleSourceSelectionPatch` | New content selection allowed;
existing/token misuse and dangling cleanup rejected; now asserts
resulting selection map. |
| `validateStyleSourcePatch` | New local source allowed; token,
duplicate/unused source and incomplete cleanup rejected; now asserts
resulting source map. |
| `validateStylePatch` | New local declarations allowed; existing design
styles, missing breakpoint and incomplete cleanup rejected; now asserts
resulting declaration map. |
| `validateMapChange` | Above three map paths, invalid patch targets,
resulting state and input immutability. |
| `validatePatchChange` | Instance/prop/style/asset/page paths and
rejected design namespaces through transaction tests. |
| `preservesNonTextChildren` and local `keepNonText` | Text-only changes
allowed; insertion/deletion/reordering of instance references and
expression changes denied; new mixed text/reference positional cases. |
| `applyContentModeTransaction` | Authorization/persistence parity,
split batches, empty transactions, scoped permissions, copy protections,
roots, cleanup, and immutable input on success/failure. |
| `materializeMdxAuthoredContent`, its `visit`, and `overlayChildren` |
MDX-source “round-trips an authored Asset Image through a styled
composite descendant” asserts src/width/height and styles after reload;
“serializes an Asset selected on an inserted Image template” asserts all
four linked props; nested and standalone component-permission tests
reject protected props. |
| `getMdxPropEligibility` | Template tests check allowed/ignored props,
aliases, incompatible types; new matching vs mismatched Image dimension
asset test exercises passed asset values. |
| `materializeMdxTemplates` | New linked-dimension test plus existing
copied-fragment, unchanged-input, authored-prop, diagnostic, and alias
tests. |
| `appendBuilderPatchChanges` | Mutation accumulator test asserts
sequential state and combined payload; merge test asserts ordering and
input preservation. |
| `mergeBuilderPatchChanges` | New repeated/interleaved namespace, empty
input, repeat-call and patch-array immutability assertions; permission
tests exercise split payload integration. |
| `getMediaAssetPropNames` | Image generates width/height/alt; Video and
unrelated components do not; explicit overrides and expression-bound
metadata retained. |
| `getPropIdsToDelete` | Image src removes dimensions; non-Image removes
only requested prop; resource cleanup and missing-instance cases. |
| `updateProps` | Atomic Image/Video updates, repeated sources
with/without saved props, overrides, bindings, original-state
preservation; new ordinary prop deduplication across names and
instances. |
| `getPatchParent` (type-only change) | Mutable nested patch and
missing-path tests. |
| `assertReplacePatchTargetsExistingPath` (type-only change) | Missing
target rejected; preceding add/root replacement makes later replacement
valid; preceding remove makes it invalid. |
| `applyBuilderNamespacePatches` | Sequential/root replacements, append
then edit, remove then replace, immutable state, permission/persistence
parity. |

Removed functions are covered through their replacements:
`shouldSyncMediaAssetProps` → shared `updateProps` plus UI E2E;
`assertContentOrBuildPayload` → transaction adapter;
`isContentModePropForInstance` → eligibility;
`isContentModePropPatchValue`/`validatePropPatch` → transition/batch
validation; `applyValidatedInstancePatch` → shared patch engine. No
behavior test is needed for import-only changes, type declarations,
public-entry exports, or regenerated metadata/docs; their callers and
contracts retain tests.

The original reporter-specific UI reproduction and image-description
product question remain separate limitations. This inventory does not
claim either is resolved.


### Latest review follow-up

- [x] Reject fresh prop IDs that shadow existing instance/property
records; cover protected bindings and valid edits
- [x] Coalesce updates in linear time while preserving last-update order
and input immutability
- [x] Check documentation/fixture impact, push, inspect CI, and reply to
both review threads


Review fixes pushed in `5dd03135e7`: 10 net production lines. Added
binding-shadow and batch-work regression assertions; no local tests or
evaluations were run, per request. Formatting and diff checks passed.
Documentation and generated fixture inputs/outputs are unchanged.
Automatic CI is pending.


- [x] Reject duplicate fresh props within the same transaction and test
split/combined batches
- [x] Preserve resource and parameter media bindings using the existing
dynamic-type check
- [x] Bound total Editor patches before validation; test the boundary
and unchanged Builder access


Review follow-up `d478446eb1`: all package, Builder, and CLI CI test
jobs and typecheck/lint/generated checks passed. Five addressed review
threads are resolved. Browser CI checks are still running. One newly
posted review about non-image assets assigned to Image sources remains
open pending the user's behavior choice. No local tests or agent
evaluations were run; local typechecks passed.


### Shared asset constraints

- [x] Reuse component file-control metadata and the UI MIME matcher in
shared property writes; preserve manually entered URLs and pending
uploads
- [x] Generate linked Image metadata only when the selected asset
supports it; cover legacy file-backed images
- [x] Add shared-runtime coverage for accepted/rejected Image and Video
assets, custom props, URLs, and atomic rejection
- [x] Review documentation, generated contracts, fixture impact, and
diff size; verify typechecks and CI (no local tests or evaluations, as
requested)


Shared asset follow-up `5f0054edd7`: property writes use the UI's
existing file-control `accept` metadata and MIME matcher. Known
incompatible assets are rejected with the approved message. Legacy image
files remain selectable without generating unsupported metadata. URLs,
custom asset props, and unresolved uploads keep their existing behavior.
Asset records are now loaded before CLI/MCP property writes; this may
require an asset-list request when not already loaded. This change does
not add asset-file downloads. Net handwritten production change: 24
lines. Builder/project-build typechecks, changed-file lint, formatting,
and diff checks passed. No local tests or evaluations were run.
Regression and runtime-entry tests were added; CI is pending. Reviewed
Image/Video docs: no guidance change needed. No fixture inputs or
generated fixture bundles changed; regenerated runtime contract metadata
only.

- [x] Keep Editor permission checks when a shared edit operation loads
assets; add an API regression test before final CI verification


Follow-up `d33b70e1c7` preserves Editor authorization when property
writes load assets. The API regression test asserts a valid Image source
reaches persistence, incompatible files are rejected, and
outside-content edits remain forbidden. This reuses the existing asset
loader and permission adapters (3 net production lines). CI found and
corrected an old session namespace expectation and a mistaken test
assumption: Video's existing unsupported `.mpg`/`.mpeg` accept entries
cause the shared matcher to allow all assets. This PR preserves that UI
behavior; it does not tighten Video formats. Total handwritten
production increase for this asset follow-up: 27 lines. Builder
typecheck/lint and server metadata generation passed; metadata output
was unchanged. CI is rerunning.


Final asset follow-up verification (`8b2efd8508`): all package, CLI,
Builder API/unit, and six browser E2E jobs passed, together with
lint/typechecks, generated checks, visual regression, migrations, size,
packaged SSG, and branch deployment. The direct API test now checks the
typed authorization cause, not an HTTP status that this test caller does
not map. All review threads are resolved. No local tests or agent
evaluations were run for this follow-up; CI provided execution coverage.
The separate Video filter limitation noted above remains unchanged.


### Shared write-boundary validation

- [x] Reuse one asset-property validator in UI/MCP property updates and
raw patch persistence, independent of permission level
- [x] Cover whole-property and field patches, component changes, invalid
metadata, final-state validation, unchanged legacy data, and stale
retries
- [x] Review documentation/fixture impact, typecheck and lint, then
verify CI; no local tests or evaluations per the earlier request

Expected change: about 70–90 net production lines. Patch saves load only
referenced asset metadata; no asset-file downloads.

Shared-save follow-up: 133 net production lines, above the initial
estimate because one save can replace an asset file and its references
together. The save validator reuses the asset writer’s canonical file
preparation rather than trusting client-declared asset types. It also
checks asset-only replacements, before folder or Build writes, while
leaving unchanged legacy references alone. Ordinary property writes load
only referenced asset metadata; asset-changing saves with references
additionally prepare the full asset map, then the writer repeats that
metadata preparation. No file contents are downloaded by the new check.
This does not change schemas, migrations, generated contracts,
documentation, or fixture inputs/outputs. Added regression coverage; no
local tests/evaluations were run, per the earlier request. Independent
Astra review found no remaining defect after the projection fix. CI
pending.

- [x] Fix selective UI sync loading to include asset-validation
dependencies; add selection regression coverage and rerun CI

The first browser run caught missing props/instances in selectively
loaded UI saves. Package/API tests passed but their fixtures provided
complete build rows. The development deployment succeeded; its
status-reporting step failed with GitHub HTTP 500.

Final verification on `a22d059717`: all package, CLI, Builder unit/API,
six browser shards, visual regression, typecheck/lint/generated checks,
migrations, size, packaged SSG, development deployment, and
Ubuntu/Windows deployment fixtures passed. Browser shard 3 passed on one
retry after a page-drag ordering assertion; the save/parsing errors were
fixed by loading the required fields. No local tests or evaluations were
run. The added persistence suite has 46 passing tests and runtime props
has 74; selective-sync tests now omit unselected fields. No unresolved
review threads. Total net production increase across this follow-up: 134
lines.


### Deep review follow-up

- [x] Make MDX linked-image validation independent of authored attribute
order; cover changed sources, mismatches, and the affected
materialization paths
- [x] Review DRY and diff size, documentation/fixture impact, and
independent review findings
- [x] Verify typechecks/lint and CI; no local tests or evaluations per
the earlier request

Review found that linked dimensions can be checked against the old
template source before the new source is applied. Fix should reuse the
shared eligibility rule with the final source, without another property
parser. Initial estimate: under 60 added non-test lines; revisit the
approach if it needs more.


Deep-review fix `f7c3d805a8`: apply Image sources before linked metadata
in all three MDX materialization paths, keeping original attribute
indexes for asset references. Reuses the existing eligibility checks and
parser; 23 net production lines. Regression cases cover source order,
changed sources, mismatched dimensions, and nested overlays. Independent
Astra review, project-build typecheck, focused lint, formatting, and
diff checks passed. No schema, documentation, generated contract, or
fixture-data changes. Local tests/evaluations have not run; focused-test
permission was requested. CI pending.


Final deep-review verification on `f7c3d805a8`: all package/CLI/Builder
test jobs, all six browser shards, visual regression,
typechecks/lint/generated checks, migrations, packaged SSG, size,
submodules, and development deployment/fixtures passed. CI ran 60
authored-content and 24 template-materialization tests successfully. No
local tests or evaluations ran; fail-before/pass-after was not executed
locally. All review threads are resolved and the worktree is clean. No
further actionable defect was found in the independent reviews. Known
remaining cost: asset-changing saves can prepare metadata twice;
removing that safely would expand this fix into persistence changes.


### Editor MCP browser E2E follow-up

`0ee978e465` adds a real CLI/MCP Editor-token round trip: insert content
into a Content Block, reload through a fresh CLI process, verify it in
the Builder canvas, and reject a protected template insertion without
changing the saved Build. A direct API denial now returns `FORBIDDEN`
instead of a generic internal error. The focused E2E and API tests,
typecheck, lint, formatting, and diff checks passed locally.
Fail-before/pass-after was verified by temporarily disabling Editor
fragment access, then restoring it. The full Luna/low evaluation run was
attempted but did not pass: the form-runtime fixture received 503
responses. The suite is not claimed green. CI for this push passed on
`0ee978e465`, including all six Builder E2E shards, package/CLI/Builder
tests, visual regression, typecheck/lint/generated checks, migrations,
size, SSG build, and submodules.


Live MCP permission probe against the branch deployment: Builder,
Editor, and Viewer role metadata matched their tokens. Editor content
text, rich text, fragment, link href, and compatible Image source edits
committed and survived a fresh MCP session. Outside-root,
protected-template, design-only prop, mismatched Image width, and
incompatible asset writes were denied without persistence. The updated
deployment returned structured `FORBIDDEN` for protected content writes
even when `contentMode` was false or omitted. Mixed valid/invalid writes
were atomic; stale Editor writes refreshed and retried. Viewer MCP could
not be exercised beyond the API gate because this Viewer token has API
access disabled. MDX frontmatter, token revocation, and UI-only
scenarios were not tested. A wrong-project token was rejected but
surfaced `INTERNAL_SERVER_ERROR`, which is a diagnostic-quality gap, not
an authorization bypass. The isolated probe page
`/codex-permission-probe-6575` remains: cleanup was stopped after a
concurrent-edit `CONFLICT`, to avoid deleting another session's work.


### Fixture-based parity follow-up

- [ ] Verify the full Content-mode UI/MCP parity matrix with an isolated
MDX/template/media/page fixture, excluding publishing and token/plan
changes
- [x] Fix the reproduced Editor MCP template/page-settings gap and cover
fail-before/pass-after routing
- [x] Verify a real CLI Editor-token create-from-template call, fresh
readback, Builder reload, allowed settings, and protected-setting
rejection
- [x] Update MCP guidance, review fixture impact, and verify required CI

The UI already allows an Editor to create a page from a template and
edit allowed page settings. MCP incorrectly required Builder permission,
and a separate MCP-only legacy Webhook Form copy guard blocked the
seeded template. Those restrictions are corrected. The server now
derives Content-mode page copying from Editor permission even when
`contentMode` is omitted or false; Builder copies retain their existing
behavior. Editor semantic commands use the server's shared Content-mode
validator, while raw Editor patches stay limited to text and editable
props. This adds a named server call and refetch for Editor semantic
edits; the existing initial project sync still occurs.

The new isolated E2E passed on `03e7ca6953`, including flag-free
template creation, settings edit, fresh CLI readback, Builder reload,
and a denied protected setting without a saved change. All required CI
passed on that commit: all six Builder E2E shards, package/CLI/Builder
tests, generated checks, typecheck/lint, visual regression, migrations,
packaged SSG, size, and submodules. Focused local runtime/API tests,
typechecks, lint, formatting, and generated-doc checks passed. Browser
E2E and full agent evaluations were not run locally; the requested
browser approval and a fresh evaluation approval were not received. The
full UI/MCP parity matrix remains open; Editor asset operations are a
separately identified gap, not claimed fixed here.


### Exploratory permission coverage follow-up

- [x] Inventory observed Builder/Editor/Viewer UI and MCP permission
scenarios against existing tests
- [x] Add fail-before/pass-after regression tests for confirmed
uncovered failures
- [x] Run focused tests, typechecks, lint, and fixture checks; verify
required CI
- [x] Record unsupported UI/MCP parity as open product work, without
calling the matrix complete

The UI share-link E2E already checks Viewer read-only controls, Editor
text editing, and Builder design controls. Existing MCP E2E checks
Editor content/template writes, page creation, asset upload, and
protected edits. This follow-up adds one real CLI/MCP flow with
API-enabled Viewer and Builder tokens: Viewer can list pages but cannot
create a page or asset folder or upload a file; Builder can change an
HTTP status setting that Editor cannot. The test checks the denied page
writes leave the build unchanged and the Builder setting survives
readback.

The run exposed two diagnostic bugs: local permission denial and REST
asset-upload denial reached MCP as generic failures. Both now retain
`FORBIDDEN`. Focused unit tests failed before these changes and pass
after; the full local browser/MCP test passes. The only non-test change
is three net lines. No fixture data, schemas, generated contracts, or
product docs changed. Local checks passed: 2,654 project-build tests, 48
HTTP-client tests, the focused Builder E2E (2 tests including setup),
affected typechecks, lint, formatting, and package boundaries.
`check:fixture-sizes` could not run locally because the SSG fixture's
built `dist/client` is absent; CI builds that fixture before checking
its size.

The full UI/MCP parity matrix remains open. In particular, Editor UI
asset update/replacement/duplication/deletion and collection-entry
creation still lack equivalent MCP support until the UI's collection
protections are enforced at the shared server boundary. Publishing and
token/plan changes were outside this test scope.

CI passed on `6490efc838`: all six Builder E2E shards,
package/CLI/Builder tests, visual regression, typecheck/lint/generated
checks, migrations, packaged SSG, size, and submodules. Shard 3 first
hit Playwright's 120-second limit under four-worker load; its logic
passed locally. The longer role flow now uses the same 240-second
per-test limit as other MCP E2E tests, and shard 3 passed in the final
run.

### Final UI/MCP parity audit

- [x] Trace Editor UI asset and folder actions through REST, runtime,
and persistence
- [x] Reproduce Editor MCP upload and folder-create denials; add
fail-before/pass-after permission tests
- [x] Add isolated Editor MCP upload/folder and UI folder-reload browser
regressions
- [x] Keep collection-sensitive asset/folder mutations Builder-only
until UI collection protections are shared at the server boundary
- [x] Review docs and fixture impact: MCP guidance is updated; no
fixture input or bundle changes
- [x] Restrict Editor folder saves to new IDs; test overwrite and delete
denials at both UI and API boundaries
- [x] Reject folder-add collisions at persistence and test the
mixed-writer/stale-snapshot case
- [x] Keep exact Editor folder retries authorized after a partial save;
add fail-before/pass-after coverage
- [x] Verify the new browser E2E in required CI
- [ ] Run the full Luna/low agent evaluations after fresh approval (not
granted for this follow-up)
- [x] Verify required CI for `6fd3beefc8`

The safe scope is small: the Assets REST upload path already authorizes
Editor tokens and enforces collection rules, but the MCP catalog
incorrectly required Builder access. Creating an ordinary asset folder
is available in the Editor UI, but the shared Content-mode save check
rejected it. Both now use Editor permission. The isolated browser tests
check fresh MCP readback and Builder reload; they have not run yet.

Further parity work remains open: Editor UI allows updates, replacement,
duplication, deletion, and collection-entry creation in cases MCP cannot
currently perform. These need the UI's collection protections enforced
at the shared server boundary before their MCP permits can be widened.
Raw Editor `apply-patch` stays text-and-props-only. This PR does not
claim the full matrix is complete.


Asset/folder follow-up `6fd3beefc8`: Editor semantic tools now share the
UI's edit permission for uploads and ordinary folder creation. Folder
saves reject same-ID changes, allow identical retries after a lost
response, and use insert for add-only persistence. The retry regression
failed before the fix and passes with JSON-normalized parent
IDs/timestamps. Independent Astra review found no remaining concrete
defect. Focused runtime (86), asset persistence (52), protocol (14), and
API/sync (71) tests passed; Builder/project-build/asset-uploader
typechecks, lint, package boundaries, formatting, generated API, and MCP
docs checks passed. Full project-build, protocol, and CLI suites passed
earlier in this follow-up; Builder core had resource-contention failures
but its affected tests passed in isolation. The new browser E2E and
agent evaluations were not run locally pending fresh approval; CI is
running them automatically.

CI passed on `6fd3beefc8`: all six Builder browser shards,
package/CLI/Builder tests, generated checks, typechecks, lint, visual
regression, migrations, packaged SSG, size, and submodules. The new
Editor MCP asset round trip and UI folder-reload tests ran in the
passing browser shards.
2026-10-23 14:40:26 +02:00
Oleg Isonen 6d73186a8b fix: Reset Form fields after successful submission (#6583)
After a successful Form submission, a visible Form returns to its
authored field defaults. The newly inserted Form starts blank, so
visitors can send another message without clearing the previous one.

## Checklist
- [x] Call the browser's Form reset after a successful Resource
submission.
- [x] Preserve entered values after failed submissions and keep
Navigation mode behavior unchanged.
- [x] Cover text, selection, and file inputs, including authored
defaults, in focused tests.
- [x] Handle authored fields named `reset` without skipping the reset or
resource refresh.
- [x] Verify Builder Preview and published-site behavior use the same
client path.
- [x] Update the Form guide for the changed behavior.
- [x] Explain the prototype reset call in a short code comment.
- [x] Run focused browser tests, typecheck, formatting, and lint on the
follow-up.
- [x] Verify required CI on the follow-up commit.

The reset test fails when the reset call is removed. The `reset` field
test failed before the review fix. Both pass now.
2026-10-22 15:40:01 +02:00
Oleg Isonen 534401c66b fix: keep publish progress after closing popover (#6570)
## Summary
- Show the existing validation success toast if validation finishes
after the Publish view closes or switches to Export. Keep it quiet while
the result is visible in Publish.
- Keep publish progress running when the popover closes. Reopening shows
a pending state without restarting the countdown and updates when
publishing finishes.
- Show a completion toast only after the Publish popover closes. Keep
errors and diagnostic findings visible across panel remounts.
- Continue polling a recovered pending build after the popover closes.
Ignore old status responses and superseded manual validation results.
- Keep a manual validation report and its “See issues” button available
after closing and reopening the Publish popover.

## Checklist
- [x] Consolidate publish progress and validation state. The PR's net
non-test change is down from +560 to +457 lines.
- [x] Cover the close/reopen validation result and suppress duplicate
publish-error toasts while Publish is open.
- [x] Run focused tests, Builder typecheck, lint, formatting, and
required CI after the simplification.
- [x] Show the existing validation success toast when validation
finishes outside the Publish view; suppress it while the inline result
is visible.
- [x] Prove the open and closed outcomes with a failing-then-passing
regression test.
- [x] Clear a persisted status error when a successful refresh finds no
build, while preserving request errors.
- [x] Prove the no-build case with a failing-then-passing regression
test and recheck docs, fixtures, and 55 focused tests, typecheck, lint,
formatting, and diff checks.
- [x] Show validation completion when the user switches to Export before
the result arrives, with a failing-then-passing regression test.
- [x] Resolve reviews and pass CI on commit 1b532637a7.
- [x] Cover countdown, completion, errors, remounts, recovery,
navigation, and stale responses with regression tests.
- [x] Keep manual validation findings and the report button visible
after closing and reopening the Publish popover.
- [x] Verify the new regression test fails before the fix and passes
after it.
- [x] Pass 37 targeted tests, Builder typecheck, lint, formatting, and
diff checks.
- [x] Review documentation impact and fixture inputs. The publishing
guide already covers validation reports; no fixture inputs or generated
outputs are affected.
- [x] Resolve all existing review threads.
- [x] Pass all CI workflows on commit 428dee3d35.
- [x] Preserve validated domain selection across popover remounts.
- [x] Ignore delayed diagnostics from superseded publish attempts.
- [x] Guard publish polling across A → B → A navigation.
- [x] Show a recovered failed build after a lost publish response.
- [x] Recheck reviews and CI on commit 51573cd848.
- [x] Suppress publish success toasts whenever the Publish popover is
open, including stale project completions and the Export view.
- [x] Verify both changed regression tests fail before the fix and pass
after it.
- [x] Recheck documentation impact and pass 38 targeted tests, Builder
typecheck, lint, formatting, and diff checks.
- [x] Suppress the validation success toast while the Publish popover
shows its validated state.
- [x] Recheck documentation impact and pass 38 focused tests, typecheck,
lint, formatting, and diff checks.
- [x] Restore validated domain selections through the visible checkbox
state and verify submitted domains match.
- [x] Recheck documentation impact and pass 51 focused tests, Builder
typecheck, lint, formatting, and diff checks.
- [x] Keep the visible checkbox selection in sync when validation
finishes after the popover reopens, without resetting a later user
change.
- [x] Resolve the late-validation review thread and pass CI on commit
3de717d993.

## Verification
- Commit 3d54f6c775 passed all four CI workflows, including all 19
main-workflow jobs and six Builder E2E shards. All review threads are
resolved.
- The close/reopen validation test failed before the fix and passes
after it. The adjusted stale-poll test failed when its ownership guard
was disabled and passed when restored. All 98 Publish feature tests,
Builder typecheck, oxlint, oxfmt, and diff checks pass locally. The
source changes do not affect documentation or fixture inputs.
- Commit 1b532637a7 passed Lint PR, Check submodules, Visual regression,
and Main workflow. All six Builder E2E shards passed. The fixture size
job passed on rerun after its first attempt failed because a React
Router fixture build could not find `build/client/.vite`. All review
threads are resolved.
- The Export regression failed before the fix and passes after it. All
38 focused tests, Builder typecheck, lint, formatting, and diff checks
pass locally. No documentation or fixture inputs are affected.
- The no-build regression test failed before the fix and passes after
it. It verifies that a request error remains visible. All 55 focused
tests and static checks pass locally; no documentation or fixture inputs
are affected.
- The validation toast regression test failed before the fix and passes
after it. All 54 focused tests, Builder typecheck, oxlint, oxfmt, and
diff checks pass locally. No documentation or fixture inputs are
affected.
- Commit 3de717d993 passed Lint PR, Check submodules, Visual regression,
and Main workflow. All six Builder E2E shards passed; shard 3 passed on
rerun after an unrelated Pages drag test failed its first attempt.
Review threads are resolved.
- The late-validation regression test failed before the controlled
checkbox update and passes after it. It checks visible state, submitted
domains, and selection after the report clears. All 51 focused tests,
Builder typecheck, lint, formatting, and diff checks passed.
- The reopened domain checkboxes and submitted form values now match.
The UI regression test failed before the fix and passes after it; it
also checks that toggling a restored checkbox changes the submitted
domains.
- The Validate action already shows “Validated” after success; the
redundant toast is removed. The existing action test covers that state.
- 38 targeted tests passed locally. Builder typecheck, oxlint, oxfmt,
and `git diff --check` passed.
- Commit 428dee3d35 passed all CI workflows, including visual regression
and six Builder end-to-end shards. Latest commit 51573cd848 passed lint,
submodule, visual regression, and the main workflow. All six Builder
end-to-end shards passed; shard 5 passed on rerun after an unrelated
Form feedback test failed, and shard 2 passed on rerun after GitHub
cancelled its first attempt.
2026-10-21 11:38:52 +02:00
Oleg Isonen 13eb7527ef fix: route deprecated Webhook Forms through Form backend (#6553)
Deprecated Webhook Forms reuse the existing Form execution pipeline
without teaching the modern backend about WebhookForm.

## User-facing behavior and how to test it
- Existing saved Webhook Forms keep their markup, action settings, and
success/error feedback. Submit a published Webhook Form and check both
outcomes.
- URL and Resource actions preserve configured methods, headers,
dependencies, body overrides, and the last submitted value for repeated
field names. Test a configured action and inspect the received request.
- Default owner notifications use the Email Worker, including submitted
files. Submit a Webhook Form without a custom action and inspect the
owner email and attachment.
- Configured Email actions validate recipients and retain threading
metadata. Test valid recipients and a rejected recipient limit.

## Implementation
- One deprecated Webhook Form adapter calls existing bot validation,
Email preparation, recipient validation, resource loading, and response
handling.
- Both generated route templates call that adapter. Both paths reuse an
internal resource-graph traversal helper; modern submission behavior is
unchanged.
- Root actions use the protected, uncached request override.
Dependencies reuse existing generated Assets and Resource caching
behavior.
- Keep reachable-resource filtering because Email preparation runs
before loading: unrelated Email settings must not break the selected
action.
- Remove dead `getDefaultActionResource` wiring from generated
templates.

## Verification
- Focused SDK tests: 116 passed (11 deprecated Webhook Form adapter and
105 modern submission, Email, and handler tests), including unrelated
resources, missing dependencies, cycles, Email preflight/threading, and
single-attempt Email failures.
- Resource generator tests: 26 passed.
- Full CLI prebuild suite: 139 passed after tracked fixture
regeneration; focused deprecated Webhook Form adapter tests: 9 passed.
- Repository fixture pipeline (`pnpm -r fixtures:build`) passed. All
tracked generated `getDefaultActionResource` references were removed,
and no Builder `N8N_FORM_EMAIL_HOOK` reference remains.
- Final MCP-generated local Preview E2E rerun: 3/3 passed.
- Focused saved Webhook Form custom-action E2E rerun: 2/2 passed (setup
and test), including a single POST through the protected test receiver
after Builder reload.
- SDK, CLI, and Builder typechecks, focused lint and formatting, and
`git diff --check` passed.
- Deprecated component picker regression test passed (2/2); verified it
fails when WebhookForm deprecation is disabled, then passes with
deprecation restored.
- Independent Astra review found no blocker, including the 62 generated
fixture changes. No production deployment or live secret change was
performed.
- Required staging CI checks passed on previous head `15635fc` (Main
workflow, visual regression, submodules, and lint). An unrelated
existing MCP success-redirect E2E assertion failed once in shard 5,
passed in a focused local rerun (2/2 including setup), then shard 5
passed on GitHub rerun without code changes.
- Terminology pass (`4a69ab573`): renamed the dedicated SDK adapter and
migration helper, updated both route templates and generated fixtures,
corrected the active identity migration marker comments, and
synchronized docs and test names. Focused tests passed: SDK 35, CLI
prebuild 12 plus 2 after final title change, MCP 7, migrations 8, React
browser 7, CLI docs 9. Seven affected package typechecks, fixture
builds, formatting, and `git diff --check` passed. That historical head
was superseded; latest-head CI results are recorded below.

- ResourceForm identity migration (`29a21edbb`): focused migration (23),
DB patch (25), project-build DB/session (93), Builder
API/import/auth/patch (60), React SDK (162), runtime/MCP (239), registry
(11), CLI evaluation (90), CLI session/sync (55), and protocol (15)
tests passed; full CLI prebuild (139) passed. Affected typechecks, lint,
formatting, and `git diff --check` passed. Independent Astra review
found no blocker. No deployment was performed.

- Prepublish Webhook Form warning (`25855e8f00`): audit checks every
non-draft published page and links nested/shared instances to the
correct page; modern ResourceForm, native forms, draft-only forms, and
orphans are excluded. Focused audit tests (13), Builder navigation tests
(6), project-build and Builder typechecks, lint, formatting, and `git
diff --check` passed. Independent Astra review found no blocker;
publishing remains nonblocking. No deployment was performed.

- ResourceForm test and CI expectation repair (`e1a527fbcc`): focused
React SDK (24), Builder preview/registry (38), project-build pages (1),
and CLI fixture API (8) tests passed; Remix and React Router targeted
browser suites, affected package typechecks, full formatting (3375
files), lint, and `git diff --check` passed. Astra review found no
blocker. Persisted identity and runtime exports were unchanged.

- Project bundle fixture repair (`70889ef27d`): eight bundles now carry
the current contract version; the two unmarked historical Form instances
in `webstudio-features` normalize to WebhookForm. Protocol fixture tests
(17), protocol typecheck, CLI fixture build, formatting, lint, and diff
check passed. Independent Astra review found no blocker.

- Story/MCP E2E CI repair (`483614e6c5`): SDK CLI story tests (7),
ResourceForm fragment tests (34), and focused local managed-form E2E (4
including setup) passed. SDK CLI and Builder typechecks, lint,
formatting, and diff check passed. Astra review found no blocker. The
initial local Preview build used an ignored stale React Router `lib`
artifact; rebuilding it restored the passing E2E run.
- Latest PR head `483614e6c5`: Main workflow [run
39317777032](https://github.com/webstudio-is/webstudio/actions/runs/39317777032)
passed, including all package matrices and six Builder E2E shards.
Visual regression [run
39317796922](https://github.com/webstudio-is/webstudio/actions/runs/39317796922),
submodules, and PR lint checks passed. The overlapping unapproved visual
run was cancelled by the approved rerun.

- Shared bot-value generator (`ecc4cad1e9`): SDK helper tests (3), React
Router WebhookForm/ResourceForm browser tests (5), SDK/Router/Remix
typechecks and builds, SDK browser-boundary check, formatting, and diff
check passed. Brave bypasses media queries, working media queries
produce a fresh timestamp per submission, and failed queries produce
`jsdom`; each managed/legacy submission has one bot field. Independent
Astra review found no blocker. Latest-head Main workflow [run
39325677117](https://github.com/webstudio-is/webstudio/actions/runs/39325677117)
passed, including all six Builder E2E shards; approved visual regression
[run
39325702835](https://github.com/webstudio-is/webstudio/actions/runs/39325702835),
submodules, and PR lint passed. The superseded visual run was cancelled
by the approved rerun.\n\n- Historical Form clipboard guard
(`e864283d1c`): instance/page transfer parsers reject ambiguous legacy
`Form` before asset import; direct fragment/page/folder mutations
preflight before changes. Canonical ResourceForm, WebhookForm, and
NativeForm remain accepted. Project-build tests (89), Builder paste
browser tests (69), both typechecks, focused lint/formatting, and diff
check passed. Independent Astra review found no blocker. Latest-head
Main workflow [run
39329184890](https://github.com/webstudio-is/webstudio/actions/runs/39329184890)
passed, including all six Builder E2E shards; approved visual regression
[run
39329562692](https://github.com/webstudio-is/webstudio/actions/runs/39329562692),
submodules, and PR lint passed. The earlier unapproved visual run
reported two expected story differences.\n\n## Compatibility notes
- Root HTTP actions reject private and same-origin destinations.
Dependencies retain their existing fetch/cache behavior.
- Routes retain the 25 MiB request limit. Shared execution applies bot
validation, a 10-second Resource timeout, and one retry for eligible
HTTP failures; an ambiguous failure can duplicate an HTTP action. Email
actions are not retried.
- Default email uses the Email Worker's sender, plain text, recipient
limits, and attachment limits instead of the legacy n8n HTML/basic-auth
path.
- Self-hosted sites need normal Email sender configuration. Direct
Builder draft Preview handling is outside this published-route change.
- Coordinate this CLI release with [SaaS PR
#669](https://github.com/webstudio-is/webstudio-saas/pull/669), which
removes the n8n helper and `N8N_FORM_EMAIL_HOOK` from new published-site
builds. Publish with a CLI artifact containing this adapter. Existing
deployed workers retain their bundled code and injected hook until
republished, so keep the actual n8n endpoint available while those sites
remain active.

## Implementation checklist
- [x] Align saved Webhook Form custom-action E2E with the protected
receiver and verify one submission.
- [x] Regenerate tracked published-site fixtures with the updated CLI
and confirm no `getDefaultActionResource` or `N8N_FORM_EMAIL_HOOK`
references remain in Builder source/config/fixtures.
- [x] Review authoritative `docs/` impact of removing the obsolete
Builder hook wiring; related deprecation and integration docs were
updated.
- [x] Route deprecated Webhook Form submissions through the shared
execution pipeline with one adapter.
- [x] Preserve configured actions, dependencies, repeated fields, body
overrides, and saved feedback.
- [x] Prepare and validate Email actions using existing shared logic.
- [x] Remove duplicated orchestration and dead generated context wiring.
- [x] Verify generated routes and MCP-generated Preview submissions.
- [x] Mark the Webhook Form component and its dedicated dependencies
visibly deprecated, while leaving shared Form infrastructure active.
- [x] Group Webhook Form-only SDK code for straightforward removal while
preserving public APIs and generated output.
- [x] Use deprecated terminology consistently for Webhook Form-only PR
code, and clarify the active identity migration marker.
- [x] Confirm required staging CI checks.
- [x] Use ResourceForm as the one active modern component identity
across Builder, SDK, runtime, templates, generated code, and docs.
- [x] Migrate historical Form, NativeForm, ResourceForm, and WebhookForm
identities in one idempotent pass using the historical pages marker.
- [x] Remove the marker from ordinary project and snapshot APIs while
preserving historical input readers.
- [x] Persist pages and instances together when a historical marked
project is saved, including a pages-only edit.
- [x] Verify migration, export/reimport, old CLI and server snapshots,
page-only save, generation, and affected typechecks.
- [x] Complete an independent architecture review before commit or push.

- [x] Warn at prepublish for deprecated Webhook Forms on published
pages, with a link to each affected element.
- [x] Verify modern ResourceForm is excluded and nested/shared Webhook
Forms navigate to the correct page.
- [x] Complete independent review, focused tests, and affected
typechecks before push.

- [x] Remove local `ResourceForm as Form` aliases from React SDK tests
and fixtures; use the canonical component name throughout.
- [x] Fix stale identity expectations found in CI and verify affected
tests/typechecks.
- [x] Complete independent review before push.

- [x] Refresh tracked project bundle fixtures to the current bundle
contract and canonical WebhookForm identity; verify protocol and
affected CLI flows.

- [x] Align SDK CLI story assertions and MCP E2E fragment fixtures with
the persisted ResourceForm component name; rerun affected checks.

- [x] Share one browser bot-value generator across ResourceForm
submission and deprecated Webhook Form wrappers, preserving field
ownership and behavior.
- [x] Verify bot timestamps, Brave/media failure handling, repeated
submissions, affected typechecks, and independent review before push.

- [x] Reject ambiguous historical `Form` clipboard fragments before any
insertion or transfer mutation, with clear recopy guidance; preserve
canonical ResourceForm action remapping.\n- [x] Verify
instance/page/template/folder paste regressions, independent review, and
latest-head CI.\n\n
- [x] Explain Email attachment size limits in the Email action UI and
give a useful error when the aggregate encoded attachments exceed the
local guard.
- [x] Check Preview and published behavior, boundary tests, and
authoritative Form docs; verify the focused change and get independent
review.

Ref #4093
- [x] Preserve safe email codes/messages through MCP, including local
attachment and upload limit errors.
- [x] Correct Form documentation for each enforced limit and verify
Preview/published/MCP paths.
- [x] Verify Builder SDK and MCP focused tests, typechecks, and
independent review before push.

- Email limits and failure copy (`6d215f3ec3`): the shared SDK checks 32
attachments and 7 MiB aggregate Base64 data before dispatch; the 25 MiB
multipart response and Email action guidance use approved copy. Managed
Email error codes pass through Form and MCP results with resource IDs,
while MCP still omits resource bodies. Form docs distinguish the Worker
7 MiB JSON cap from Cloudflare's 5 MiB complete message and
verified-destination exception, and correct the no-retry behavior.
Focused SDK tests 89, Builder API tests 44, UI browser tests 42,
SDK/Builder/CLI/docs typechecks, lint, formatting, and generated MCP
docs check passed. Astra found no blocker. No real email or production
deployment occurred.

- Latest Email limits head `6d215f3ec3`: Main workflow [run
39393037290](https://github.com/webstudio-is/webstudio/actions/runs/39393037290)
passed on rerun, including all six Builder E2E shards; approved visual
regression [run
39393456618](https://github.com/webstudio-is/webstudio/actions/runs/39393456618),
submodules, and PR lint passed. The initial unapproved visual run showed
the same two previously accepted story differences.

- [x] Address review: normalize previous-Builder `Form` instance patches
before persistence, preserve historical WebhookForm identity, and cover
marked/unmarked cases. Documentation impact reviewed: this restores
intended save behavior, so no user guide change is needed.

- Review finding follow-up (`ce05dd733a`): three Form identity patch
regressions passed red/green; full project build-patch tests 28/28,
project typecheck, lint, formatting, and diff check passed. Independent
Astra review found no blocker. Incoming PATCH `Form` is treated as the
immediately previous Builder's modern form alias; already stored
unmarked historical `Form` continues to migrate to WebhookForm. The old
patch protocol has no client-release identifier, so pre-October-14 live
clients adding new ambiguous IDs cannot be identified separately.

- Latest review-fix head `ce05dd733a`: Main workflow [run
39397337356](https://github.com/webstudio-is/webstudio/actions/runs/39397337356)
passed, including six E2E shards after a targeted shard-4 rerun for an
unrelated MDX slash-menu timeout. Approved visual regression [run
39397395206](https://github.com/webstudio-is/webstudio/actions/runs/39397395206),
submodules, and PR lint passed. The unapproved synchronize visual run
was cancelled by the approved rerun.
2026-10-21 00:25:55 +02:00
Oleg Isonen fc38f71ba2 docs: Document self-hosted email tRPC interface (#6542)
Document the email service interface used by the Builder's server-side
tRPC call.

- Specify `POST /trpc/email.send`, bearer authorization, project-ID
metadata, direct JSON input, attachment encoding, and the tRPC
delivery-result envelope.
- Document `TRPC_SERVER_API_TOKEN` and `EMAIL_SERVICE_URL`, including
the current restriction to approved Webstudio Worker origins. The docs
do not claim that an arbitrary self-hosted origin can be configured
today.
- Keep the endpoint contract focused on its external HTTP interface.

Validation:
- A clean-room implementation from the original REST wording returned
404 for the Builder's `/trpc/email.send` request.
- I discarded it, revised the docs, then tested from scratch using the
actual Builder `createCloudflareManagedFormEmailSenderWithUrl` helper
from this PR head against a local fake tRPC endpoint. Success and
delivery-failure responses passed; the emitted URL, method, headers, and
direct JSON payload matched the contract.
- Confirmed the actual Builder helper rejects an arbitrary self-hosted
URL. The endpoint used a fake provider; no email was sent.
- The focused SDK managed-form email suite passes: 57 tests.
Documentation only; no deployment.
2026-10-17 02:38:56 +02:00
Oleg Isonen 2c20db0d01 fix: Parse font shorthand with variable families (#6551)
## Summary

- Parse `font-size / line-height` structurally so a custom-property font
family does not swallow the line-height.
- Add parser, shorthand-expansion, and fragment-insertion regression
coverage.
- Document the semantic conversion sequence for composite media,
preserving wrappers while deleting obsolete provider parts.

Closes #6547
Closes #6544

## Verification

- `pnpm --filter @webstudio-is/css-data test -- src/shorthands.test.ts
src/parse-css.test.ts` — 4,326 passed, 1 skipped
- `pnpm --filter @webstudio-is/project-build test --
src/runtime/components.test.ts src/runtime/instances.test.ts` — 181
passed
- CSS data, project-build, and CLI typechecks
- CLI docs generation
- GitHub Actions: all workflows passed (including all six Builder E2E
shards)

## Checklist

- [x] Regression tests added for the reported behavior.
- [x] Docs impact reviewed; MCP manuals and generated docs updated.
- [x] Diff and generated documentation verified.
2026-10-17 02:21:05 +02:00
Oleg Isonen 37429b2aad docs: Refresh Variables section and dialog guide (#6543)
## Change
- Match documentation navigation and links to the Builder **Variables**
section label, including the Flotiq setup path.
- Refresh the Variables guide against the current new variable dialog:
available types, name validation and shadowing, resource editors,
previews, and actions.
- Preserve “data variables” where it names the data concept and keep
existing Resource and GraphQL link anchors.

## Verify
- `pnpm check:generated-docs` passed.
- `git diff --check` passed.
- Reviewed the docs-only diff against the dialog editors and current
labels.
2026-10-17 00:58:30 +02:00
Oleg Isonen 33f7b350bc fix: Improve Form setup and email settings guidance (#6536)
## User-facing features and how to test them

- Project Settings → Emails → Reply to has an info tooltip explaining
how its display name is used in visitor confirmation subjects and
messages, and when “Site Owner” is shown. Test it by hovering or
focusing the info icon.
- When a Form has no resources available for its Action, clicking the
Action + button explains that a resource must first be created in
Variables. Test it with a Form that has no action-capable resources,
then create a resource and verify it can be added to the Action.
- Success redirect is shown inside the Action control only when Resource
actions are connected. Test by switching between navigation mode and a
Form with a Resource action; the redirect field should appear only for
Resource actions, and its saved value should return when a Resource
action is added again.
- The Action control no longer offers Disable/Enable. Connected Resource
actions run in their configured order. The Form Action model has no
`enabled` field; older saved JSON with that extra key is read as an
ordinary connected action.

## Verification

- Focused UI test for the Reply to tooltip.
- Focused UI test for the empty Action resource tooltip.
- Manually verify Success redirect visibility and value retention while
switching modes.
- Manually verify all connected Actions execute in order, including
older saved Actions carrying an ignored `enabled` key.


## Review follow-up

- Address all current open code review comments on this PR, verify the
fixes, and reply to each addressed thread before resolving it.

## CI repair follow-up

- Inspect the latest failing checks on this PR, fix the causes on the
existing branch, and verify the affected checks locally and in CI.

## Follow-up audit (2026-10-14)

- Audited head `8fb0b44`, all inline review threads and the PR
conversation. The earlier Reply to tooltip comment was answered and
resolved. Two P1 comments remained: required boolean `enabled`
validation in CLI evaluations, and execution of saved disabled actions.
- `builder-e2e (shard-6)` failed because Preview returned `Disabled`
between `First` and `Last` in the configured failure results test. The
failure matches the security review finding. `builder-e2e (shard-2)` was
still in progress at audit start; the staging deployment workflow was
cancelled.
- The follow-up filters saved disabled actions from execution and
restores boolean `enabled` validation in both CLI evaluations. Builder
labels saved disabled actions. Focused SDK (55), CLI (222), and Builder
(45) tests, plus all three affected package typechecks, passed locally.
- Pushed `7c2b90e082`; replied to both P1 review threads, then resolved
them. Its main CI run passed all 25 test/check jobs, including all six
Builder E2E shards and the previously failing shard 6.
- A later P2 review identified stale CLI/MCP guidance. Commit
`160a9d1707` corrects the source and generated docs; focused docs tests
(12) and CLI typecheck pass locally. Replied to and resolved that
thread. On this latest head, the main CI run completed successfully: 27
check jobs passed, including all six Builder E2E shards; one
visual-regression job was skipped. All three required checks passed. The
automatically triggered staging deployment workflow was cancelled under
the no-deployment instruction.


## Input type default follow-up

- Show `text` as selected in the Props panel for both the HTML `input`
element and the Input component when their `type` prop is unset,
matching their rendered default. Verify both variants in the Builder and
retain explicit type choices.


## Current review-thread follow-up (2026-10-14)

- Recheck every unresolved review thread against the current PR head,
make and validate any required fixes, and reply with the resolution
before closing each thread. Keep this work on the existing PR branch; do
not deploy.


## Input type metadata follow-up (2026-10-14)

- Move the unset input `type` display default into component/element
property metadata for both the Input component and native HTML `input`
element. Remove the Props-panel special case, keep the implicit default
out of saved props, and cover both variants with regression tests.
- Run focused checks, push the fix to this PR, and cancel any
automatically triggered staging deployment workflow. Do not deploy
manually.


## Props metadata selector simplification follow-up (2026-10-15)

- Refactor `$selectedInstancePropsMetas` after the input default
metadata change. Preserve the current metadata, ordering, and attribute
override behavior while simplifying the selector's structure and
abstraction boundaries.
- Run focused checks, commit and push to this PR branch. Cancel any
automatically triggered staging deployment; do not deploy manually.

## Latest review follow-up (2026-10-15)

- Audit the latest PR head, all unresolved inline review threads, and
general PR comments. Address actionable feedback on this branch, run
focused validation, then reply and resolve each addressed thread. Cancel
any automatically triggered staging deployment; do not deploy manually.


## Form Action model decision (2026-10-15)

- Remove the Form Action `enabled` field completely from the type,
serialization, validation, execution, fixtures, and current docs. Accept
older saved JSON containing that extra key by ignoring it, including
`enabled: false`; action presence and valid IDs determine execution.
- Update the CLI evaluation to assert intended action IDs and order
without using `enabled`. Address the latest P2 review finding against
this model. Run focused checks, push on this PR branch, and cancel any
automatic staging deployment. No manual deployment.


## Action control visual follow-up (2026-10-15)

- Match the Form Action plus button to the established Variables section
plus button style and implementation where appropriate. Keep the
existing icon.
- Give the Action resource dropdown a stable width comparable to the
Variables resource dropdown, including when available resource names are
short.
- Add focused UI coverage, validate locally, commit and push to this PR
branch, and cancel any automatic staging deployment. Do not deploy
manually.


## Action plus hover follow-up (2026-10-15)

- Match the Action `+` button's hover and focus styling exactly to the
plus buttons in panels such as Variables. Compare their actual
implementations, preserve the existing icon, and add focused UI coverage
where appropriate.
- Run focused checks, commit and push to this PR branch. Cancel and
confirm any automatically triggered staging workflow; do not deploy
manually.


## Current CI fix (2026-10-15)

- Inspect the latest check rollup and failing job logs on the current PR
head. Fix all current failures on this branch, run focused local
validation, push the changes, and monitor the new head until checks
complete.
- Cancel and confirm any automatically triggered staging deployment. Do
not deploy manually.
2026-10-15 17:25:22 +02:00
Oleg Isonen 9a0c24eef2 docs: clarify HTML Embed asset URLs (#6532)
HTML Embed snippets can use root-relative paths to project assets. This
update explains which HTML attributes Webstudio rewrites, when duplicate
asset paths are left unchanged, and that script bodies and CSS URLs are
not rewritten. It also clarifies that the canvas preview may differ from
the published page.

- [x] Document supported asset URL attributes and limitations
- [x] Narrow the canvas preview comparison

Related to #6518; this documentation PR does not close the issue.
2026-10-14 20:04:42 +02:00
Oleg Isonen 59d4f00ce7 feat: new Form component, all features and improvements you dreamed of (#6485)
## User-facing features and how to test them

In Components → Forms, insert **Form (Beta)**. The existing Webhook Form
remains a separate component.

- **Insert a ready-to-use contact Form.** In Components → Forms, insert
**Form (Beta)**. It starts with accessible Name, Email, Subject,
Message, and optional Attachment fields; sends submitted field values
and uploaded files to the account holder; sends a customizable
confirmation to the visitor without attaching the submitted file; and
includes success and error feedback. The two Actions are named **Email
to owner** and **Email to visitor**. *(#3023, #5499)*
- **Use the contact Subject in both email subjects.** Owner and visitor
subjects bind the submitted Subject input; the visitor subject also
identifies the sender. Template and SDK tests pass.
- **Format submitted Form data readably in Email bindings.**
`formData.toString()` now renders labeled lines, nested values, and
uploaded filenames in any Email Resource expression, while excluding
passwords and internal Form fields. SDK and independent-bundle tests
pass. Published staging delivery is verified in Batch 7.
- **Choose Form actions.** Select the Form and open Properties &
Attributes → Action. Add up to ten visible HTTP, GraphQL, or Email
Resources; reorder, disable, or remove them. Submit and confirm enabled
actions run together. *(#5499)*
- **Send submitted fields and files.** Submit repeated fields and a file
input to an HTTP Resource. Check the received multipart fields, order,
filename, type, and bytes. Email Resources attach submitted files by
default; turn off **Attach submitted files** to omit them. *(#3023,
#5432)*
- **Make the visitor confirmation friendly and signed.** Inherited
content gets the approved friendly message and resolved sender name;
custom text remains intact. Email Request preview and delivery use the
same resolver. SDK and Builder preview tests cover defaults, custom
overrides, blank content, sender identity, and preview parity. Suggested
body:

  > Thank you for your message.
  >
  > We received it and will respond as soon as possible.
  >
  > Webstudio Team
- **Identify the sender in the visitor confirmation.** The resolved
sender name appears in the visitor subject and body. Focused tests pass.
- **Include source page context in the owner email.**
`browserInfo.pageUrl` is included in the owner message and the Email
Request preview. Focused SDK and Builder tests pass.
- **Use Form and visitor data.** In a Resource defined on the Form, bind
`formData` and `browserInfo` in its URL, headers, or body. Local MCP
Preview tests verify submitted fields, browser, and language. *(#5428)*
- **Use page and query values.** On a dynamic page with query
parameters, bind `system.params` and `system.search` in a Form Resource,
then navigate and submit. A query key with one value is a string;
repeated values are an ordered array. Confirm the current route values
are available after navigation. Text bindings render repeated values as
comma-joined strings. *(#5512, #3891)*
- **Use Navigation mode for search and filters.** Remove every Resource
from the new Form’s Action list. Its action becomes
`?__webstudio__=navigate`, and submitting named fields updates the URL
query and page Resources without a full page reload. Reconnect a
Resource to return to Resource submission mode
(`?__webstudio__=resources`). A plain Element with tag `form` keeps
native browser behavior.
- **Customize notification email.** In Project Settings → Emails, set
recipients, sender, and Reply to. In each Email Resource, choose project
recipients, custom addresses, or a visitor email input, and configure
its Reply to, subject, body, and attachment setting. Verify a
per-Resource override does not change another Form’s settings. *(#4414,
#4385, #5658)*
- **Choose Resource types from one ordered list.** In Variables → Type,
find REST resource, GraphQL resource, Assets resource, Email resource,
Sitemap resource, and Current date resource in that order. Confirm the
labels and order in the Type dropdown.
- **Inspect Resource requests and responses.** Open Variables → a
Resource → Edit variable. Use **Load data** on Request when no request
is shown; inspect HTTP request, response, and diagnostics. For Email,
load the request preview and confirm it does not send an email. After
submission, inspect saved HTTP and GraphQL exchanges without triggering
another request. *(#4093 related; new Form Resource inspector)*
- **Explain the Email Response before a submission.** Refresh builds the
local request preview without sending an email; the Response tab tells
users to submit the Form in Preview to see the Email Service response.
Focused inspector test passes.
- **Use action results in the Form.** Bind `formState`, `results`, and
`errors` in the success and error content. Submit successful and failing
actions and verify each result or error includes its action’s response
details in configured order. *(#5499)*
- **See submission feedback.** Submit a Form whose success or error
message is outside the viewport and confirm that message scrolls into
view. Repeat with feedback already visible, reduced motion, partial
failure, and a configured success redirect; visible feedback should not
move, and redirect should happen without scrolling first. *(#6487)*
- **Redirect after success.** Set Success redirect and submit both
successful and failing Forms. Only success follows the redirect.
*(#3530)*
- **Keep separate email threads.** Retries preserve a submission
reference, while separate submissions appear in separate Gmail
conversations; Batch 7 verifies both. *(#5018)*
- **Keep existing Webhook Forms.** Insert the existing Webhook Form and
verify its configured behavior remains intact. For its documented
query-binding fixes, verify route params and repeated search values
after navigation. *(#5512, #3891; #4093 and #5432 remain related legacy
issues)*

- **Keep native Form props out of the new Form editor:** The new Form
metadata hides generated rendered-tag HTML props such as `method`,
`encType`, `target`, and ARIA attributes, while its own Action, Success
redirect, and visibility controls remain. The legacy Webhook Form
behavior is unchanged. A Builder property-selector regression test
verifies this.

- **Allow up to 10 Form Actions:** The shared validator and Action
picker now allow up to 10 Resource actions, and the Form control
description reflects the limit. SDK tests accept 10 and reject 11; the
generated CLI prebuild rejection coverage passes (130 tests). SDK and
SDK Components typechecks, formatting, and diff checks pass.

- **Make the default Form ready to use without edits:** The inserted
contact Form has required Name, Email, Subject, and Message fields; its
project Email Action resolves the current project contact address or
account holder dynamically, with no personal data in the template; its
visitor Email Action has a confirmation subject and message; success and
error states give actionable feedback. `core-templates.test.tsx`
verifies fields, routing, and copy; focused registry tests and typecheck
pass.

- **Make the default Form usable as a contact form:** Its Email Actions
are named “Email to owner” and “Email to visitor.” The owner body binds
the complete submitted `formData` object rather than manually listing
fields, and an optional Attachment input sends its file only to the
owner. Email Resource expressions render `formData.toString()` as
readable labeled lines. The visitor receives a confirmation without
submitted files. Registry/template tests and MCP E2E verify the inserted
Form, owner message and attachment, visitor message, and Request
preview.

- **Format Form action errors clearly:** The default Form shows each
action error as a spaced bullet inside a red-tinted alert panel.
Registry template tests verify the alert and list structure; 8 template
tests pass.
- **Deprecate the existing Webhook Form before its next-PR removal:**
The component metadata and compatibility exports in the React, React
Router, and Remix packages are marked deprecated, and the guide says
removal is planned. Keep form submission helpers still used by
NativeForm active; remove legacy-only wrappers and template in the
follow-up PR.

## MCP feature parity test plan

Run these cases through Webstudio MCP on a disposable project. Create
and configure the Form with MCP, save and read back the configuration,
then test its generated Preview. Use only a controlled test mailbox for
real email delivery. This plan mirrors the user-facing UI cases above.

- Discover **Form (Beta)**, inspect its schema, insert the default
contact Form, and read back its fields, Actions, success/error content,
and absence of personal data.
- Change the submitted Subject and Name. Verify owner and visitor
subjects, visitor sender identity, friendly confirmation text, and the
owner page URL.
- Bind the complete `formData` object. Verify readable lines, nested
values, filenames, and exclusion of password/internal fields.
- Add HTTP, GraphQL, and Email Actions; reorder, disable, and remove
them. Verify concurrent dispatch, result order, partial failure, and the
10/11 Action limit.
- Submit repeated fields and one or multiple files to HTTP. Verify
multipart order, filename, MIME type, and exact bytes. Verify Email
attachments default on, then turn them off for the visitor Action and
confirm the visitor receives no file.
- Configure project email settings and Resource overrides. Save/reload
each recipient mode, Sender, Reply to, subject, body, and attachment
setting; verify overrides do not leak between Forms.
- Bind `formData` and `browserInfo` from a Form-scoped Resource. Verify
page URL, browser, language, and hosting-provided IP/referrer where the
test environment supplies them.
- Test dynamic route params and single/repeated query values before and
after navigation. Verify Navigation mode updates the URL and bound page
Resources without a full reload; reconnect an Action and verify Resource
mode. Check native `form` Element behavior separately.
- Verify Resource discovery order and method labels, then exercise
HTTP/GraphQL Request, Response, Diagnostics, load/refresh, and saved
exchange inspection. For Email, verify request preview sends nothing and
the Response appears after actual submission.
- Bind `formState`, `results`, and `errors`. Submit success, validation
failure, partial Action failure, and full failure. Verify feedback
content, visible/off-screen scrolling, reduced motion, and success-only
redirect behavior.
- Submit twice and verify separate email threads and stable retry
identity, with no duplicate delivery after an ambiguous send.
- Verify the existing Webhook Form remains editable and its route/query
behavior remains intact, while new Form discovery/creation and native
Form prop exposure match the documented component contract.

### Execution status and MCP limitation

The `mcp-*` suite authors through a current-source MCP server and
submits generated Preview Forms through `preview.submit-form`.
Playwright covers Builder UI, navigation/rendering, and viewport
assertions. The separate `forms.simulate-submission` operation submits
synthetic values from the saved canvas Form directly to the shared SDK
server Form engine, without starting Preview or a browser. Both paths
use the same saved-draft preparation and SDK submission handler. Most
automated email tests use a loopback receiver. The controlled
branch-source MCP test below separately verifies delivery to a real test
mailbox; it does not test the user’s connected MCP release.

On 2026-10-14, the branch-source MCP was tested against a running local
Builder and a copied disposable project. No npm package publish or
connected-MCP release was needed for this local verification.

- [x] Add a bounded `preview.submit-form` MCP operation for
session-owned generated Preview Forms. It uses native form controls and
validation, supports repeated values and bounded file uploads, returns
state/results/errors/exchanges, and never replays an ambiguous send. The
MCP-client E2E verifies one owner Email delivery and one HTTP delivery
with ordered results against local receivers. Focused Vision, Project
Build, CLI, and Builder E2E tests pass.
- [x] Migrate browser Preview Form runtime cases to
`preview.submit-form`; retain Playwright for Builder UI,
navigation/rendering, and viewport assertions. The 52 MCP-named Builder
E2E cases passed across the full 2-worker run (50/52) and a focused
rerun of its two timed-out Email cases (2/2), including MCP submission
and local receiver assertions.
- [x] Add `forms.simulate-submission` to the current-source CLI MCP
session. It reads the saved canvas Form, calls the Builder server route,
and reuses the Preview Form engine and SDK submission handler. The older
local CLI adapter rejected Email; the current server route supports it.
Static Checkbox and multiple Select array names come from saved
controls; dynamic array names may be supplied explicitly. Focused tests
and independent Astra Medium review pass.
- [x] Run the Luna Low `managed-form-runtime-v1` scenario against
`forms.simulate-submission` through a real MCP client on the current
source. `gpt-5.6-luna` at low reasoning passed 10/10 checks in 55
seconds. Three simulation calls produced success, an explicit 503
failure, and recovery against the controlled local HTTP receiver in the
expected sequence. The agent used no Preview/browser submission or Email
action. One `templates.get` MCP error was recovered before submission.
- [x] Verify `forms.simulate-submission` from branch-source MCP against
the local Builder server and a disposable saved Form. The supported
local database was started, and `EMAIL_SERVICE_URL` plus
`TRPC_SERVER_API_TOKEN` were supplied from the staging Infisical
environment without printing values. Luna Low used MCP discovery,
Form/resource inspection, `update-resource`, and simulation. Both Email
Resources resolved only to `testing@webstudio.is`; simulation returned
success, HTTP 200, two Email results with status 200, and no errors.
Gmail independently confirmed both owner and visitor messages delivered
to that controlled inbox at 15:46:51 UTC on 2026-10-14. The shared
server route and CLI transport received independent Astra Medium review;
focused CLI (164) and HTTP-client (45) tests, package typechecks,
formatting, and diff checks pass. No npm release or production
deployment was involved.

- [x] Rename the new Form component label to **Form (Beta)** in the
component picker and MCP discovery. Component IDs stay unchanged;
focused registry, Builder, MCP, and CLI tests pass; Astra Medium
reviewed the change. Pushed `592589ccfe`.

## MCP end-to-end verification

### Verified against this branch

These checks use the current-source MCP session server to author
disposable local Builder projects and `preview.submit-form` to submit
generated-site Forms to local test receivers. Playwright verifies
Builder UI, navigation/rendering, and viewport behavior. They verify
this branch's code, not the connected MCP release, staging services, or
real mailbox delivery.

Current branch rechecks (2026-10-14): Project Build MCP tests 142/142,
CLI MCP/evaluation harness tests 60/60, Vision CDP/Form tests 48/48, and
the React Router Navigation browser test 4/4 pass. Builder, CLI, Project
Build, Vision, and both React Router fixture typechecks pass; scoped
lint, formatting, and diff checks pass. The earlier head passed PR CI;
checks on commit `7438692ef5` are running. The separate full CLI
prebuild suite now passes 131/131 after adding the required `statusText`
to its private Email binding fixture. The main-merge CI failures in
Project Build expression expectations, CLI Preview config import,
formatting, and the SSG fixture size cap were fixed in `0272768260`;
checks on that commit are pending.

- [x] Verify all 52 MCP-named Builder E2E cases on the current head. The
complete 2-worker run passed 50/52; two complex Email cases exceeded
Playwright's 120-second per-test limit under concurrency, then both
passed in a focused one-worker rerun (44.4s and 31.0s; 2/2). A serial
full rerun reached 43/52 without assertion failures before the wrapper's
900-second command limit. An earlier full run passed 52/52 before the
final Preview readiness fix. Coverage includes contact Forms, Email
delivery and partial updates, attachments, readable `formData`, HTTP and
GraphQL Actions, navigation and repeated query values, legacy Form
editing, Resource inspectors, Form properties and type choices,
feedback, and redirects. Full-run command:
`E2E_TEST_COMMAND_TIMEOUT_SECONDS=1800 E2E_SKIP_BUILDER_BUILD=true
E2E_INSTALL_PLAYWRIGHT=false pnpm --filter @webstudio-is/builder e2e --
'mcp-' --project=chromium --workers=2`.
- [x] Run the Builder Preview Form E2E suite serially: 5/5 passed,
including authenticated Builder Preview submission, error rendering,
redirects, submitted browser `FormData`, off-screen feedback scrolling,
and Navigation mode without a Resource POST or canvas reload.
- [x] Verify the local Email inspector server path. The route
integration suite calls the real local `/rest/preview-form` handler and
passes a controlled Email Service binding response through it; 28/28
tests pass. The Email Service boundary is stubbed, so this does not
claim a staging Worker run.
- [x] Verify the Form Action picker browser tests: 2/2 pass for add,
disable, reorder, remove, the ten-Action limit, and rejection of an
eleventh Action. The MCP runtime E2E separately verified ten deliveries
and ordered results.
- [x] Verify local Preview request context across two pages: scoped
`formData`, `browserInfo.pageUrl`, trusted IP, user-agent, and language
all remain associated with the correct Form Resource.
- [x] Verify partial Email body updates through MCP. Custom and
explicitly blank bodies preserve the other recipient, sender, and
attachment settings; local Preview uses the requested body. Focused E2E
passes 2/2.
- [x] Verify the default Form template has no hardcoded email address,
sender, or recipient, and the error Collection renders each message as a
separated bullet in a red-tinted alert. Registry tests pass 11/11.
- [x] Verify the Action picker UI adds, disables, reorders, and removes
HTTP, GraphQL, and Email Resources; accepts 10 and blocks 11. Component
browser tests pass 2/2. Runtime MCP E2Es separately verify Action order
and concurrent dispatch; the picker test does not claim ten live
deliveries.
- [x] Verify the Luna Low managed-Form authoring evaluation: 20/20
checks pass. Validator tests pass 83/83.
- [x] Verify the legacy Webhook Form is excluded from new MCP
creation/copy paths while saved instances remain editable. Source tests
and the existing 139 MCP checks pass; this does not mean the published
MCP catalog has been refreshed.
- [x] Formatting, `git diff --check`, and Builder, CLI, and SDK
Components Registry typechecks pass.

## Runtime correctness

- [x] Prevent automatic retry of an Email Action when the Email Service
send may already have succeeded; report an ambiguous response without
sending the same email again, and verify one Form submission cannot
produce duplicate delivery. Pushed `07627e44fa`: Email Actions never
automatically replay; HTTP retries remain. RPC and Preview accept legacy
`{id}` and canonical replies, including successful canonical replies
with an empty provider ID. Regression tests cover one dispatch after
failures, timeouts, and malformed replies, response compatibility, and
HTTP retry. All 143 focused SDK tests, SDK typecheck, scoped lint, diff
checks, and independent Astra review pass.

- [x] Keep ambiguous Email Action outcomes non-retriable in Builder so a
possibly successful send is never replayed; preserve compatibility with
both deployed legacy `{id}` and canonical `{ok, status, statusText,
data}` Worker replies. Pushed `578a1a99`: canonical delivery IDs must be
nonblank, while legacy replies remain supported. SDK regressions cover
one dispatch after ambiguous RPC/Preview failures, timeouts, malformed
replies, and empty IDs.

## New Form navigation mode and MCP E2E (2026-10-11)

- [x] Give the new Form two submission modes using its valid action URL:
`?__webstudio__=resources` when one or more Resources are connected and
`?__webstudio__=navigate` when none are connected. Removing the last
Resource switches to Navigation mode. Keep a plain `form` Element native
and leave the legacy Webhook Form available but marked deprecated until
the next release. Runtime tests cover both action markers; the
MCP-authored browser E2E verifies navigation query updates and Resource
revalidation. Legacy Webhook Form metadata, compatibility exports,
component code, and docs now state its deprecation and support window.
- [x] In the new Form’s Action control, when no Resources are connected,
show centered “Navigation mode” with an info tooltip: “Submits the form
fields as URL query parameters and navigates without a full page reload.
Use this for search and filters that update page Resources from the
URL.”
- [x] Add MCP-authored end-to-end coverage for both modes and their
distinctions. Luna/low authored Resource and Navigation Forms plus a
native `form` Element through MCP, with separate registry searches for
Form (Beta), Webhook Form, and Element; the final project passes the
corrected structured evaluator. The MCP-authored Playwright E2E removes
all Form Actions and verifies repeated and single query values update
the URL and page Resource without a document reload or Resource POST.
Existing local MCP Preview coverage verifies Resource submission, native
GET behavior, legacy Webhook Form compatibility, and component
discovery.

## Ports from the earlier implementation

- [x] Resource selection and scoped `formData`/`browserInfo` access,
parallel dispatch, protected egress, server destination limits, and
bot-field validation are already in the new Form.
- [x] Resource editor URL autofocus and known header/value suggestions
are already reused.
- [x] Centralize the `formData` and `browserInfo` parameter names for
Builder, generator, and runtime checks.
- [x] Default HTTP body forwarding and automatic multipart for uploads
are now ported to the new Resource graph.
- [x] Success/error state slots and success redirect are now ported into
the new Form and Router/Remix adapters.
- [x] Add response `results`/`errors` bindings and one retry per failed
or timed-out destination.
- [x] Port feedback scrolling to the new Form in Router and Remix
adapters, with browser regression coverage.
- [x] Adapt the existing bot-field protection for the new Form; saved
Webhook Forms keep their prior bot-field behavior. The narrowly scoped
route/query fixes for #5512 and #3891 are tracked separately below.
- [x] Local new-Form bot checks: one fresh hidden value per submission,
five-minute expiration, malformed/missing rejection before dispatch,
Brave handling, and stripping internal form ID/bot fields before
Resource delivery. Evidence:
`packages/sdk/src/managed-form-submission.ts`,
`managed-form-submission.test.ts`,
`packages/sdk-components-react/src/form-submission.browser.test.ts`, and
managed generated-route cases in `packages/cli/src/prebuild.test.ts`.

## Batch 1 — New Form foundation

- [x] Make the existing Forms-section **Form** tile insert the new Form
component while retaining the existing Webhook Form tile and behavior.
- [x] Make the new Form a managed Resource submission component. With no
destination, it shows a configuration error and cannot dispatch or
navigate. Use Element with `tag=form` for ordinary browser form
behavior.
- [x] Exclude unrelated legacy-only runtime work from this feature.
Existing component metadata is deprecated; the new Form has no legacy
compatibility or migration contract. The targeted #5512/#3891
route/query fixes are tracked separately in Batch 2a.
- [x] Verify the Resource-only component with focused render tests,
package typecheck, and component story generation.
- [x] Verify the Batch 1 new Form acceptance criteria in the Builder and
browser.

### Acceptance criteria

- Inserting the new Form creates the default contact Form with named
inputs, a submit button, and two selected Email Resource Actions: one
for project recipients and one for the visitor email field. If all
destinations are removed, it shows a configuration error and cannot
dispatch or navigate until a destination is configured.
- Element with `tag=form` retains browser validation, method, encoding,
action, and navigation.
- Acceptance covers the new Form only. Apart from the explicitly tracked
#5512/#3891 route/query binding fixes in Batch 2a, legacy compatibility,
old native-mode settings, and old-component migration are outside the
feature contract.

**Verification:** Builder catalog and insertion tests; Chromium
empty-Resource blocking test; generated Element `tag=form` Playwright
test with a local receiver
(`packages/sdk-components-react/src/element-form.test.tsx`,
`40c6f972c8`, focused 1/1) proves invalid input sends nothing and valid
native multipart POST navigates using the configured action, method, and
encoding; generated published output for the new Form; relevant
typechecks and lint.

The user confirmed the test inbox received the test email attachment.
Staging Builder Preview verified Email and Webhook delivery. The user
confirmed the published staging route is live; the route returns HTTP
200 and includes a file input. Published delivery to both Webhooks and
final Form-state behavior are verified in Batch 7 below. Live Production
Form submissions are outside this PR’s verification scope.

## Batch 2 — Resource-only submission, scoped data, and Resource
selection

- [x] Give the new Form one submission path with one to ten selected
Resource destinations. Keep Actions and Success redirect as its Form
controls; plain HTML forms use Element with `tag=form`. An empty
selection reports an error without dispatch or navigation; tests cover
this path.
- [x] Expose one submission-only `formData` object, keyed by HTML name.
Repeated names are ordered lists; a known unchecked checkbox group is an
empty list. Preserve files and hidden inputs.
- [x] Expose safe `browserInfo`: trusted IP, user agent, language, and
referrer. Never expose raw request headers, cookies, or authorization.
- [x] Make explicit Resource expression bindings to a Form's `formData`
and `browserInfo` available only to Resources defined within that Form;
a selected HTTP Action with an unset or empty body implicitly receives
submitted Form data regardless of Resource scope.
- [x] Resource variables are created in **Variables** at their normal
scope. The Actions selector lists only action-capable Resource variables
visible in the Form's variable scope: defined on the Form or one of its
ancestors. Exclude sibling, descendant, and unrelated scopes. Resources
defined on the Form can bind its `formData` and `browserInfo`; ancestor
Resources cannot bind values from a descendant Form. Carry forward URL
autofocus and known header/value autocomplete. Enforce up to ten Actions
per Form in the Builder and server.
- [x] Keep **Actions** as a control inside **Properties & Attributes**,
with a plus menu and selected Resource rows. The plus menu offers only
existing action-capable HTTP, Email, and GraphQL Resource variables
visible in the Form's current variable scope (defined on the Form or an
ancestor); exclude System data variables and resources from sibling,
descendant, or unrelated scopes. Resource variables are created in
**Variables** at their normal scope; Actions only selects existing
variables. Disable already-selected Resources in the menu. Selected rows
expose enable, disable, and remove controls. A simple click on the
Action name opens the existing Resource editor through selection/open
state, leaves its Variables row visibly selected, and scrolls it into
view, while preserving page and canvas selection; drag, enable/disable,
and remove controls must not open it. The eye toggles enabled state; the
minus removes the Action from this Form only. Preserve explicit
selection, ordering, and the ten-Action limit. Published-site submission
checks are recorded in Batch 7.
- [x] Cover scope, expressions, repeated names, renames, files, copying,
deletion, save/reload, and unavailable bindings with focused tests.
- [x] Ensure `mapResourceExpressionsMutable` remaps Email
`senderExpression` and `recipientsExpression` data-source IDs;
`fragment.test.tsx` verifies copy/paste/fragment rebinding (focused
regression 1/1, full file 34/34). Project-build typecheck and `git diff
--check` pass.
- [x] Add focused fragment coverage for Email `senderExpression` and
`recipientsExpression` bound to variables inside a copied Form,
asserting references remap to the cloned variable IDs (not only
restoration of external parent bindings by name). `fragment.test.tsx`
passes 34/34 and combined project-build data+fragment tests pass
165/165; assertions verify both expressions remap to cloned variable IDs
distinct from the source IDs.
- [x] Implement and locally test Batch 2a current-page parameters and
repeated query values in managed and legacy Form Resource bindings
(#5512, #3891).
- [x] Add generated published-route loader integration coverage for a
native GET Form with repeated query keys; repeated `system.search`
values reach an expression-bound Resource in both `defaults` and
`react-router` templates. Focused prebuild tests pass 2/2.
- **Issue scope:** #5512 is fixed for legacy generated HTML Form actions
and the new Form path; generated-route tests cover both route templates.
#3891 exposes repeated values as ordered arrays through `system.search`
while preserving strings for single values; SDK, Builder Preview,
generated action, and generated native-GET loader regressions cover the
behavior.
- [x] Local #5512/#3891 implementation and regression: the new Form's
generated action uses current `system.params` and `system.search`, where
repeated values are ordered arrays; SDK search tests and the Builder
native GET preview test preserve repeated values
(`packages/cli/src/prebuild.test.ts`,
`packages/sdk/src/system-search.test.ts`,
`apps/builder/app/canvas/interceptor.test.ts`).
- [x] Locally verify #5428: bind trusted `browserInfo` values into
`X-Forwarded-For`, `User-Agent`, and `Accept-Language` Resource headers,
inspect the generated action’s outbound request, and document the setup
and trusted client-IP source for each hosting adapter without forwarding
cookies or authorization.
- [x] Local generated-route verification (`e9d5209882`): Remix and React
Router send the three configured headers to a test destination;
Cloudflare context uses `CF-Connecting-IP`, Node omits unavailable IP,
and arbitrary forwarding, cookie, and authorization headers are not
forwarded. Setup and hosting sources are documented.

### Form property inventory

- [x] Expose only the Actions control and **Success redirect** as Form
controls. The generic HTML attribute picker and native `id`, `class`,
`action`, `method`, `target`, and `encType` controls are unavailable for
this component.
- [x] Rename the Form control label from **Actions** to singular
**Action**.

Staging verification for the Form property panel is recorded in Batch 7.

### Acceptance criteria

- Action and Success redirect are the only Form controls. The component
label `Form` is system metadata and is not editable as an HTML
attribute; the Form has no `id`, `class`, `action`, `method`, `target`,
or `encType` controls.

- **Action** is a control inside **Properties & Attributes**. Its plus
menu offers only existing action-capable HTTP, Email, and GraphQL
Resource variables visible in the Form's current variable scope (defined
on the Form or one of its ancestors). It excludes System data variables
and Resources in sibling, descendant, or unrelated scopes. Resource
variables are created in **Variables** at their normal scope; Actions
only selects existing variables. Already-selected Resources are disabled
in the menu. Selected Resources appear as rows with enable, disable, and
remove controls, and can be reordered. Clicking an Action name opens the
corresponding Resource’s existing Edit variable dialog and highlights
its Variables row by setting the existing selection/open state directly,
without synthetic clicks; preserve the current page and canvas
selection. Drag, enable/disable, and remove controls do not open the
editor. The ten-Action limit is enforced. Resources defined on the Form
can bind that Form's `formData` and `browserInfo`; ancestor Resources
are visible but cannot bind values from the descendant Form. An empty
selection shows no canvas warning before submit; attempting to submit
shows the configuration error, dispatches nothing, and does not
navigate. The Form has no native or direct-URL submission mode.
- The new Form supports Resource Actions only; do not add a
legacy/native-mode compatibility path or migration requirement.
- A Resource defined on the Form can bind that Form's `formData` and
safe `browserInfo`; a visible ancestor Resource cannot access values
from its descendant Form. Resource variables are created in Variables at
their normal scope. The plus menu lists only existing action-capable
Resources from the Form and ancestor scopes, excluding sibling,
descendant, unrelated scopes, and System variables. Forms explicitly
select each action; none are auto-added. Already-selected Resources are
disabled in the menu. Bindings survive editing and save/reload.
- On a `/products/:slug` page visited as
`/products/chair?source=newsletter`, a submitted Resource can use
`system.params.slug` as `chair` and `system.search.source` as
`newsletter` in its URL, headers, or body. Values come from the current
submission's page, including after navigation; missing parameters remain
absent.
- Repeated names retain form order, unchecked checkbox groups resolve to
empty lists, and files stay file values. The Resource editor focuses URL
and suggests known header names and values.

## Batch 2a — Keep page parameters and every selected query value
(#5512, #3891)

New Form Resource bindings must receive the current page's parameters,
and a multiple-select GET form must not lose selections when a binding
reads the URL.

- [x] Extend #5512 route/query propagation to legacy native Form
actions: generated routes pass route params and ordered query values
while preserving the managed Form path. The regenerated runtime contract
and 26 fixture routes are pushed in `8c4bda7791`; `check:generated-api`,
fixture builds, and generated-route regressions pass.
- [x] Implement route/query propagation in both managed and legacy Form
Actions: populate `system.params` from the current route, preserve
single-value `system.search` strings and expose repeated query values as
ordered arrays through the same binding. Generated action and loader
regressions cover both templates; the consolidated CLI prebuild suite
and focused native-GET loader tests pass.

- [x] Verify #5512 with the new Form's Resource bindings using
`system.params` and `system.search`. Cover URL, header, and body
bindings, dynamic pages, missing values, and submission after navigation
in React Router and Remix.
- [x] Reproduce #3891 with a native GET form and a multiple-select
input. Keep the browser's standard repeated query keys; do not join
values with commas or rewrite the submitted URL.
- [x] Use `URLSearchParams` to preserve every value in order, including
empty values, repeated identical values, and values containing commas or
encoded characters. Keep an object-based binding interface so property
autocomplete still works.
- [x] Inspect existing `system.search` consumers and record the
compatibility choice here. Keep existing single-value bindings working,
expose all repeated values to bindings, and make any changed behavior
for existing repeated-key bindings explicit. Do not silently discard
values or break existing expressions.
- [x] Apply the agreed representation consistently in the Builder,
published page data, client navigation, and new Form action path. Update
affected types, binding suggestions, generated code, and fixtures.
- [x] Add passing SDK, Builder, and generated-route regressions for
current page parameters and repeated query values in new Form bindings;
document single-value and ordered multi-value query bindings with
working examples.
- [x] Local new-Form generated-route regressions cover current page
parameters and repeated query values in URL, headers, and body after
navigation. A red-before/green-after test proves configured body
bindings survive alongside submitted fields; CLI binding examples
document single and repeated query values.
- [x] Record regression coverage for #5512/#3891 on new Form bindings:
generated-route and Builder tests cover current-page parameters and
ordered repeated, empty, comma, and encoded query values, with SDK
coverage for scalar compatibility.

**Compatibility choice:** `system.search[name]` is a string when the
query key occurs once and an ordered string array when it occurs more
than once. Existing single-value expressions are unchanged; text
bindings stringify arrays as comma-separated values. The relevant
consumers in Builder system/preview and generated page/action templates
were reviewed. Focused SDK, Builder, and generated-route tests cover the
behavior.

### Acceptance criteria

- On a `/products/chair?source=newsletter` page, a new Form Resource can
bind `system.params.slug` as `chair` and `system.search.source` as
`newsletter` after navigation. Existing Webhook Form behavior is
unchanged except for the explicitly tracked #5512/#3891 route/query
binding fixes in Batch 2a.
- Selecting two options in a native GET form leaves both values in the
URL and makes both available to bindings in their original order.
Single-value access still works, and property autocomplete remains
available.
- Empty values, identical repeated values, commas, encoded characters,
reloads, and client navigation do not drop or corrupt query values.
- Verify current-page and repeated-query bindings for the new Form and
ordinary Element `tag=form` browser behavior. Legacy Webhook Form
changes are limited to the explicitly tracked #5512/#3891 route/query
fixes in Batch 2a; other legacy compatibility is not a closure
condition.

## Batch 3 — HTTP Resource submission

- [x] POST to each selected HTTP Resource. A selected Action whose
Resource body is unset or empty implicitly receives all submitted fields
regardless of Resource scope; an edited body can omit, rename, add, or
compute fields. Explicit `formData` expression bindings remain limited
to Resources defined within that Form.
- [x] Use JSON for text-only data and switch to multipart when the
resolved body contains a file. Set JSON content type unless configured
otherwise; preserve repeated fields and file bytes, and remove stale
`Content-Type` so fetch writes the multipart boundary.
- [x] Add an explicit `auto`/JSON/multipart choice. Reject an
incompatible body before that destination's fetch; dependency-free
requests are preflighted before any destination dispatch.
- [x] Resolve bindings and validate final outbound URLs and redirects
before sending. Block internal and private destinations; keep
credentials and sensitive request configuration off the public page.
- [x] Add generated-site and server tests for JSON, multipart, mapping,
files, repeated fields, invalid configuration, and destination safety.
- [x] New Form route proof: compiled Remix and React Router managed
actions forward repeated same-name text values and uploaded files as
separate multipart entries, preserving order, duplicate values,
filenames, MIME types, and bytes (`3c5b72a`; focused CLI tests passed
2/2). Existing Webhook Form behavior remains at its pre-PR baseline;
migration is out of scope.
- [x] Make file inputs easy to configure in the Builder: choose the file
input type and set its field name, required state, accepted file types
(`accept`), and multiple-file selection (`multiple`). Keep these
settings after saving, reopening, and publishing. Verified on
`d7f67307f9` with Builder insert/save/reopen, generated page, Chromium
required/optional/multiple upload, SDK template, typecheck, lint, and
format checks.

### Acceptance criteria

- Every selected HTTP destination receives POST. A Resource with an
unset or empty body receives all Form data implicitly, regardless of
where the Resource is defined. Explicit `formData` expression bindings
remain limited to Resources defined within that Form. Text-only bodies
arrive as JSON; bodies with uploads arrive as multipart with field
names, filenames, MIME types, file bytes, and repeated values.
- A site creator can add and configure a file input using the Builder
without custom code. Published forms support one or multiple selected
files, block submission when a required upload is empty, and submit
normally when an optional upload is empty. The `accept` setting guides
the file picker; it is not server-side file validation.
- An edited body can rename, omit, add, or compute fields. A Resource
outside the Form sends only its own configured data.
- An incompatible explicit body format fails before its destination
fetch. Dependency-free requests are validated before any dispatch; a
body that depends on another Resource is validated once that dependency
resolves, before its own destination fetch. Resolved destinations and
each redirect reject private or internal addresses, and secrets stay off
the public page.

## Batch 4 — Project Emails settings and Resource editor

- [x] Add Project Settings → **Emails**. Move the current Contact email
list without changing its stored value.
- [x] Configure site-wide defaults for Sender (name and email, or email
alone), recipients, owner subject, and plain-text owner body. Published
sites receive project default changes after republishing.
- [x] Add the Email Resource as a normal Resource, creatable inside or
outside a Form. It can inherit, override, and reset project defaults.
Only a Form-scoped Email Resource can bind that Form's data. Surface an
unavailable Form-only binding clearly; never send incomplete template
text silently.
- [x] Resolve team recipients from the project list or owner fallback,
support a custom comma-separated override, and default Sender to the
owner with a per-Resource override. Form data cannot choose team
recipients.
- [x] Configure the subject, plain-text owner body, and whether
submitted files should be attached. With no project body or Resource
body, any selected non-Visitor Email Resource uses readable submitted
fields and optional `browserInfo` interpolation, regardless of Resource
scope. Automatic owner-email text includes hidden fields while excluding
passwords and internal bot fields.
- [x] Test project settings storage, inheritance/override/reset,
recipient fallback, Form scope, expression validation, and preservation
of the existing Contact email setting. A complete project owner body
containing `${...}`, backslashes, or backticks renders as literal text;
an empty project body retains the automatic `formData` and `browserInfo`
interpolations.

### Acceptance criteria

- Existing Contact recipients appear in Project Settings → Emails
without data loss. Existing legacy forms retain their pre-PR behavior
except for the explicitly tracked #5512/#3891 current-page route and
repeated-query bindings.
- New Email Resources inherit published project defaults until
overridden; reset restores inheritance, and settings edits affect live
sites only after republishing.
- Team recipients use the project list or fall back to the owner. A
custom comma-separated recipient list and Sender override work;
submitted values cannot choose recipients or From.
- With no project owner body or Resource body, a selected non-Visitor
Email Resource uses readable submitted-field and `browserInfo` content
regardless of whether it is defined on the Form, an ancestor, or
globally. Explicit expressions can bind Form-only values only from
Resources defined within that Form. A nonempty project body is the
complete literal owner message, inherited until a Resource body
expression overrides it; reset restores inheritance. Automatic text
includes hidden form fields but excludes passwords and internal bot
fields.

## Batch 4a — Sender and recipient email format (#4414, #5658)

Use one **Sender** field that accepts either `Acme Support
<support@example.com>` or `support@example.com`, using the same address
format as recipients. The optional name becomes the displayed sender
name, and the entered email receives replies. Webstudio's authenticated
From address stays fixed.

- [x] Add the Sender field in Project Settings → Emails, with
inheritance, override, and reset in each Email Resource. Default it to
the project owner's email. This single field supplies the display name
and Reply-To; do not add separate sender-name and Reply-To inputs.
- [x] Parse Sender as exactly one mailbox and retain its optional name
and reply address; a plain email has no custom display name.
- [x] Use the site owner profile username as the default visible From
name when Sender has no explicit name; explicit Sender names take
precedence, and missing usernames fall back to “Site Owner”. Keep the
authenticated From address fixed.
- [x] Accept plain and named mailboxes in the Contact email list,
project recipients, and Email Resource recipient overrides.
- [x] Verify existing project address-count limits remain enforced
through both Builder and server updates; the separate five-team-delivery
runtime cap is tracked in Batch 5.
- [x] Use `Acme Support <support@example.com>` as the Sender placeholder
and `Acme Support <support@example.com>, team@example.com` in email-list
inputs.
- [x] Use an established email-address parser in Builder and server
validation. Handle quoted names containing commas, such as `"Example,
Alexs" <support@example.com>`, reject malformed addresses and
header-injection input, and count parsed recipient mailboxes including
duplicates for the runtime delivery cap.
- [x] Preserve parsed display names in generated Email Resource requests
and keep existing plain-address settings readable.
- [x] Test both Sender formats, mixed recipient parsing,
inheritance/reset, generated request values, and the five-team-delivery
runtime cap.
- [x] Local Builder E2E verifies Project Emails Sender and named
recipients save to persisted project settings and remain after reopening
(`b9ebf21fee`;
`apps/builder/e2e/tests/project-settings-runtime.[shard-2].[shard-5].[shard-6].e2e.ts`;
focused run 2/2).
- [x] Document the Sender/recipient input format, reply behavior,
inheritance, republishing, visitor Email Resource delivery, and the
current Cloud-unavailable delivery status without claiming that saving
settings sends email.
- [x] Local generated-graph verification (`1730840feb`): a translated
project body is the entire literal request body, a Resource body
expression overrides it, reset restores inheritance, and an empty
project body retains automatic `formData`/`browserInfo` text. Project
Emails help and docs explain plain-text behavior.

### Acceptance criteria

- Entering `Acme Support <support@example.com>` in Sender displays “Acme
Support” as the sender and directs replies to `support@example.com`,
while From uses Webstudio's sending address.
- Entering only `support@example.com` works without a separate name
field and directs replies to that address.
- With a plain Sender address, the visible From name uses the site owner
profile username, or “Site Owner” if unavailable; a named Sender
overrides it. The From address remains Webstudio’s fixed sending
address.
- Plain and named recipients can be mixed in a list; a quoted comma
inside a name does not create another recipient.
- Names and addresses survive saving, publishing, and delivery, and
existing plain-address settings continue working.
- The project owner confirmed that the email customization already
planned in Batches 4 and 4a is sufficient to close #4414 and #5658.
Sender supplies the display name and reply address; Webstudio remains
the authenticated sender. Do not add custom From addresses,
sending-domain verification, or a separate follow-up requirement to
close these issues. Do not describe Reply-To as a custom From address.
- Site creators can edit the notification subject, plain-text template,
content, and language, with project defaults and per-Resource overrides.
Together with Batch 4, this covers the email customization requested in
#4414.

## Batch 5 — Email delivery and visitor confirmation

- [x] Implement the Cloud-only `EMAIL_SERVICE` adapter for new Email
Resources. Attach every submitted file by default; preflight the 5 MiB
encoded-message and 32-attachment limits and return explicit service
errors. Staging published-Form Email delivery is verified in Batch 7.
- [x] Implement visitor confirmation as an ordinary Email Resource
selected in Actions, addressed through a submitted visitor email field
and dispatched alongside other Actions. Its optional body starts empty
and uses the existing Resource editor and expression bindings. Email
Service prepends a mandatory confirmation preamble with the site URL.
Remove the Form-level confirmation toggle and email-field selector; SDK
and generated-route tests cover delivery.
- [x] A failed visitor-addressed Email Resource appears in `errors` and
does not change aggregate Form success; SDK tests cover this.
- [x] Before any destination runs, count team-recipient deliveries
across all reachable Email Resources, counting duplicates and shared
dependencies once. Reject the whole submission above five at runtime;
permit at most one visitor-addressed receipt separately. No
cross-Resource Builder or publish-time cap substitutes for this
preflight.
- [x] Local subject validation (`1fa8e66d9d`): resolved Email Resource
subjects must be strings without CR/LF. Validate after expressions are
evaluated and before any selected destination dispatch. SDK tests cover
CR/LF from direct `formData` and `browserInfo` subject bindings and zero
provider or selected fetches
(`managed-form-resources-generator.test.ts`,
`managed-form-submission.test.ts`, `resource-loader.test.ts`; focused
tests 88/88 passed). Dependency-bound Email is rejected after any
required lookup and before selected destination dispatch.
- [x] Verify resolved owner and visitor-confirmation subjects in the
generated server path with provider fakes. Owner subject preflight
covers CR/LF from visitor-derived bindings before any selected
destination or provider send; confirmation subject preflight likewise
sends no HTTP or email request. Published staging delivery is verified
in Batch 7.
- [x] Test the local private-service envelope and response path with a
provider fake: recipients, Reply-To, optional Sender display name, plain
text, attachments, recipient and size/count preflight, privacy,
confirmation ordering, partial failure, quota error, and zero sends on
failed preflight. Compiled Remix and React Router action tests cover
confirmation with an HTTP sibling and Node's explicit unsupported
response.

### Cloudflare Email Sending integration plan

Companion SaaS PR:
[webstudio-is/webstudio-saas#666](https://github.com/webstudio-is/webstudio-saas/pull/666).
The Email Service contract uses the canonical Worker
`webstudio-email-service`, Cloudflare's native per-location Rate
Limiting binding, and the private `EMAIL_SERVICE` service binding for
published-site sends to `https://email-service.internal/v1/send`.
Builder Preview uses the authenticated shared Email Service endpoint
`https://apps.webstudio.is/v1/preview-send` with server-side
`TRPC_SERVER_API_TOKEN`. The shared Email Service Worker remains
deployed at version `8769175b-15d2-4c09-a312-b78dc93fd2f3`; this PR did
not deploy or change it. The named staging Preview was deleted and its
former URL returns 404; staging published-site Workers use the canonical
Worker through the private service binding. An authenticated malformed
production Preview request returned `400 invalid_request` / `Invalid
email fields` before send. This proves only authentication and
validation, not successful submission or delivery. Staging
published-Form delivery is verified in Batch 7; limiter verification is
tracked in companion SaaS PR #666. A separate direct Email Service test
delivery was confirmed received; no email was sent by the
malformed-request preflight.

- [x] Enforce Cloudflare's 5 MiB **total encoded message** limit and
32-attachment cap for ordinary outbound email, accounting for base64
expansion; report provider quota, suppression, sender, size, and count
errors without claiming delivery. These email limits are separate from
the Form's 25 MiB incoming upload limit and Webstudio's five
team-delivery preflight. Local Builder preflight/provider-fake tests and
the companion SaaS Worker tests cover the limits and stable error
mapping; staging published-Form delivery is verified in Batch 7.

- [x] Test the local generated-site private-service contract with
provider fakes for recipients, Reply-To, optional Sender display name,
body, attachments, confirmation timing, partial failures, quota/limit
errors, and zero sends for failed preflight
(`packages/sdk/src/managed-form-email.test.ts`,
`packages/cli/src/prebuild.test.ts`; `7507c3fbf7`, `ab04be99d4`,
`7c950eecec`).

#### Acceptance criteria

- Staging verification must prove the selected private call path works
for a Workers for Platforms published site; the central Worker alone
owns `send_email`, and a submitted Form never receives provider
credentials or direct sending capability.
- A Cloud published Form delivers owner mail and the optional
confirmation with the configured envelope and content; 5
MiB/32-attachment violations and provider failures produce clear
`errors`. HTTP siblings retain the existing partial-failure semantics.
- The Email Service contract is for the new Form only. Existing Webhook
Forms do not use this Email Service as part of this feature; no legacy
Email Service compatibility or migration is included.

### Acceptance criteria

- A Cloud Email Resource sends the configured plain-text subject and
body from Webstudio's sending address with the configured display name,
with every submitted file attached under its original filename and MIME
type, or returns an explicit size/count error.
- More than five total team-recipient deliveries across Email Resources
rejects the whole submission before any destination runs; duplicate
addresses count again.
- A visitor receipt is a normal Email Resource explicitly selected in
Actions and addressed to the submitted visitor email field. It runs in
parallel with the other selected Actions, with no separate Form-level
toggle or email-field selector. Its Resource body is empty by default;
the Email Service always prepends the fixed platform receipt and
published site URL before the Resource body. The Resource body uses the
normal editor and full existing expression/binding support. A failed
visitor Email Resource appears in `errors` but is nonfatal to the
aggregate Form result.
- Resolved owner and confirmation subjects are strings without CR or LF.
An invalid subject, including one produced from visitor data, is
rejected before the email provider is called; the provider fake records
no send.
- Provider integration checks exercise the central Email Worker
request/response contract. Mocked Builder delivery alone does not
satisfy this gate.
- Confirmation failure appears in `errors` while the aggregate
submission remains successful; environments without Cloud email do not
silently claim delivery.

## Batch 5a — Keep separate submissions in separate email threads
(#5018)

Site owners should see each form submission as a separate email
conversation, including when several visitors submit the same form close
together.

- [x] Append a short, unique submission reference to each new Form
owner-notification subject, keeping the configured subject text.
Generate it once per submission and reuse it for that submission's
delivery retries; submissions in the same second get different
references. At `52fcec3640`, SDK tests cover default/custom subjects,
two submissions at one mocked time, retry stability, empty/overlong
preflight with zero HTTP/email sends, and the 979-character boundary;
Form and Project Emails docs describe the suffix. Batch 7 verifies that
separate submissions create separate Gmail conversations.
- [x] Apply per-submission subject references to new Form owner
notifications (`52fcec3640`).
- [x] Add local regressions for new Form default/custom subjects,
same-time identical submissions, stable retry references, and
subject-length preflight (`managed-form-email.test.ts`). Document the
suffix in the new Form and Project Emails docs.

### Acceptance criteria

- Two separate submissions of the same form appear as separate
conversations in Gmail with conversation view enabled.
- Both default and custom subjects keep their configured text and
include a different reference for each submission.
- Retrying delivery within one submission keeps its reference. This does
not add delivery deduplication or submission storage.
- This submission-reference behavior applies to new Form owner
notifications only; it does not add delivery deduplication or submission
storage.

## Batch 6 — Parallel delivery and Form feedback

- [x] Resolve dependency Resources needed for bindings, preflight every
selected primary destination’s body format and deterministic URL
parse/protocol/credentials/denied-hostname policy before dispatching any
selected primary destination, then start selected primary destinations
in parallel. Dependency Resources may make outbound requests during
resolution. DNS/public-IP pinning and redirect checks remain in
protected transport at each request hop; other transport failures can
occur after a sibling dispatches. Overall success requires all primary
destinations to succeed.
- [x] Local deterministic group preflight (`e9b72d5d78`, `8bf4f4c8bf`):
red-before generated actions in Remix and React Router prove a dependent
JSON+File body or a denied selected URL blocks all selected POSTs; SDK
policy tests cover allowed URLs and denied scheme, credentials, and host
cases. Nonselected dependencies may run once to resolve bindings.
Provider and ingress behavior are covered by service tests; staging
verification is recorded in Batch 7 below.
- [x] Until Cloud email delivery is integrated, reject any selected
Email Resource or reachable Email dependency before dispatching an HTTP
sibling. Return an explicit unsupported-delivery error without claiming
success.
- [x] Retry only a failed or timed-out destination once within that
submission, including non-2xx responses. Never resend a known success
within the same submission. A new browser submission starts over; keep
no delivery history or cross-submission deduplication.
- [x] Keep aggregate HTTP status as transport metadata only; expose no
`status` variable. Bind each Resource's final status code and response
body through ordered `results` and `errors` arrays. Expose no response
headers.
- [x] Redirect only after overall success when configured. Render the
initial, success, and error states in the inserted Form template.
- [x] Fix #6487 feedback auto-scroll regression: success and error
feedback now scrolls into the viewport only when needed, uses instant
behavior for reduced motion, and success redirect skips scrolling.
MCP-authored generated-Preview tests assert actual viewport position for
off-screen feedback, no movement for visible feedback, partial failure
scrolling, and no scroll before redirect; the full serial MCP E2E suite
passes (36/36 including setup).
- [x] Copy the existing Webhook Form spam protection into the new Form's
managed submission path: generate the hidden browser field, validate it
on the server before delivery, preserve the Brave handling, and remove
internal fields before sending to Resources or email.

**Email Service rate-limit checkpoint:** The pushed SaaS code uses
Cloudflare's native Rate Limiting binding only on email sends: 50
recipient checks per project UUID per Cloudflare location per 60
seconds. Each recipient consumes a check; over-limit sends return 429
before provider delivery. Preview and published sends carry the trusted
project UUID. This is approximate per location, with no Durable Object,
SQL storage, or account-wide quota. The 60-attempt direct Worker
diagnostic and isolated non-production check are tracked in companion
SaaS PR #666; no live-user Form submission is part of this Builder PR.
- [x] Locally test concurrent starts, retries, timeouts, partial
success, results and errors bindings, redirect, and feedback scrolling
in both generated adapters and browser components.
- [x] Local new-Form behavior: generated Router and Remix adapters test
concurrent selected starts, one retry after non-2xx/network/timeout
failure, final partial-success `results`/`errors`, and no replay of
successful siblings or dependencies
(`packages/cli/src/prebuild.test.ts`). Builder insertion tests verify
`results` and `errors` bindings and confirm aggregate `status` is not
exposed as a variable
(`apps/builder/app/shared/instance-utils/insert.test.tsx`); browser
tests cover success-only redirect and visible/offscreen feedback for the
new Form.

### Acceptance criteria

- Primary destinations begin in parallel. Each failed or timed-out
destination gets one retry within the same submission; known successes
are not resent, and overall success requires every primary destination.
- Ordered `results` and `errors` expose each Action Resource's
`resourceId` and `resourceName`, final status code, and response body to
bindings, without response headers. Both arrays preserve configured
Action order even when responses finish out of order. Aggregate HTTP
status remains transport metadata and is not exposed as a Form variable.
- Redirect occurs only after overall success, without scrolling to
inline feedback first. Otherwise the correct feedback state renders and
scrolls into view only when outside the viewport. Already-visible
feedback does not move the page, and animated scrolling respects reduced
motion. This applies to the new Form.
- New Form submissions use the adapted hidden browser check: valid
browser and Brave submissions pass; missing, malformed, or expired
evidence stops delivery before any destination runs; internal bot fields
never reach Resources or email.
- The Email Service code returns 429 for recipient checks over the
native per-project-UUID, per-location 50-per-60-second limit before
provider delivery. HTTP/Webhook Actions and webhook-only Forms are not
throttled by it. The 60-request direct Worker diagnostic and webhook
control are tracked in companion SaaS PR #666; no production Form
submission is part of this Builder PR.

## Batch 6a — Legacy-only work excluded from this feature

### Acceptance criteria

- The new Form accepts a fresh valid submission after an earlier failed
attempt without stale bot data.
- No legacy compatibility path is added to the SaaS Email Service.

## Batch 6b — Shared submission runtime for the new Form (related to
#4093)

Use shared new-Form submission behavior across supported hosting
runtimes while keeping page data current after a submission. This batch
does not complete #4093's remaining legacy Webhook Form work.

- [x] Move new Form submission, pending/error/success handling, and
response handling into shared code that does not require Remix or React
Router hooks or their form components. Generated hosting adapters call
the shared managed-Form handler; existing Webhook Forms keep their
original behavior.
- [x] Local new-Form lifecycle: router-free Form submission uses shared
`submitFormData`; generated Router and Remix adapters supply the server
request and page-data refresh behavior. Legacy compatibility adapters
and lifecycle changes are not included.
- [x] Submit through a normal HTTP endpoint with a defined JSON response
and small Router/Remix request and page-data-refresh adapters. Keep
current HTTP Resource credentials, validation, bot checks, and
destination protection on the server.
- [x] Local managed-Form route: generated same-origin `/__ws-form` POST
returns structured JSON for new Form submissions in compiled Remix and
React Router handlers; route coverage includes root, nested, and dynamic
pages, repeated multipart fields/files, bot rejection, and Resource
results. Staging Cloud email delivery is verified in Batch 7; the Email
Service rate-limit verification is tracked in companion SaaS PR #666.
- [x] Integrate the Cloud `EMAIL_SERVICE` adapter for new Email
Resources into the generated server action, with provider-fake response
and preflight tests. The Email Service per-site rate limit is tracked
separately below.
- [x] Replace the automatic page-data refresh currently supplied by
Remix. After a successful submission changes data, refresh the current
page's Resources without a full-page reload. Handle navigation during
submission and refresh failures without resending a completed
submission.

- [x] Test the new Form's shared component without router context
against a test HTTP endpoint, then test both hosting adapters. Cover
response bindings, page-data refresh, files, redirects, errors, and
retries.
- [x] Local browser and generated-route matrix: router-free Form tests
cover pending/results, files, and HTTP/network failures; generated
Router and Remix adapter tests cover retries, redirects, loader refresh,
navigation, and refresh failure without replaying POST. Compiled-handler
tests cover both adapters; release/staging verification is complete in
Batch 7.
- [x] Real router-free HTTP test (`679eb8e0c275`,
`packages/sdk-components-react/src/native-form-http.test.tsx`): the
shared new Form runs without a router provider in Chromium and sends
multipart POST to a local same-origin JSON endpoint without mocking
`fetch`. It reports pending while the response is held, then success
with `results` and a later error with `errors`, without native
navigation. Release/staging verification is complete in Batch 7.
- [x] Page-data-refresh red-before/green-after proof: with the exact
`native-form-refresh.browser.test.tsx` files from `20989a2d64` placed
temporarily in detached pre-fix `41580dbe9c`, React Router and Remix
each failed 3/5 tests: successful submissions left displayed data at
`before`, and the refresh-failure path never ran its second loader call.
On PR head `679eb8e0c275`, both adapters pass 5/5. The temporary
checkout was restored cleanly; the red/green regression evidence is
recorded here.
- [x] Local new-Form regression tests cover pending/results, errors,
redirect behavior, selected destinations, retries, and page-data
refresh. Remove legacy Webhook-specific retry, feedback, lifecycle, and
converter claims; they are outside the PR scope.
- [x] Update the hosting and Form docs to explain the generated server
endpoint, dynamic hosting requirement, and static-export limitation. The
generated Cloud action calls the private `EMAIL_SERVICE` binding when
present. Staging published-Form delivery is verified in Batch 7.

### Acceptance criteria

- The shared submission component works without a Remix or React Router
provider; supported hosting adapters connect it to server delivery and
page-data refresh.
- A successful data-changing submission refreshes the displayed data
without reloading the whole page or sending the submission again.
- Resource response values remain available through the bindings from
Batch 6, with consistent success/error behavior across supported
runtimes.
- The generated new-Form submission path uses the shared handler;
existing CLI conversion and legacy submission behavior remain at their
pre-PR baseline except for the #5512/#3891 route/query binding fixes in
Batch 2a.
- Server protections and secret handling remain intact. Static exports
do not silently claim managed submission support.

## Batch 7 — New-Form integration, docs, and release gate

- [x] Mark only the legacy `Form` and `RemixForm` component metadata
deprecated and keep `NativeForm` as the sole Forms insert tile. Preserve
the old component IDs, registrations, templates, and supported settings
for saved instances (`820aed68ed`;
`apps/builder/app/shared/nano-states/components.test.tsx`, focused
Chromium 2/2).
- [x] Local file-input and HTTP upload segments: Builder
insertion/save/reopen retains name, required, accept, and multiple
settings (`apps/builder/app/shared/instance-utils/insert.test.tsx`);
browser tests cover required, optional, and multiple files
(`packages/sdk-components-react/src/native-form.browser.test.tsx`);
compiled Remix/React Router generated actions preserve repeated
multipart fields and original file names, MIME types, and bytes
(`packages/cli/src/prebuild.test.ts`, `3c5b72a`).
- [x] Partial published-site staging check: one controlled `/copy-1`
submission with unique test value `Staging E2E 2026-10-05` entered the
managed Form success state for the selected project-owner Email Resource
configured with subject `New form submission`. The exact wire HTTP
response and owner inbox receipt are not independently verified; the
user is checking that inbox. This does not close the full
multi-destination, file, confirmation, coverage.
- [x] Update Form, Resource, Project Emails, and hosting docs for File
Input settings, HTTP uploads and limits, visitor-addressed Email
Resource with mandatory confirmation preamble/site URL and optional
body, and plain HTML forms using Element with `tag=form`.
- [x] Document the current managed Form's combined 25 MiB upload-request
limit, multiple-file failure guidance, and separate HTTP Resource body
limit (`33b2c7eab9`); Email provider attachment limits are documented in
the Email Service README, and published staging delivery is verified in
Batch 7.
- [x] Document local Resource selection/scope and Auto/JSON/multipart
upload behavior, plus Project Emails Sender, recipient, owner-body,
inheritance, and visitor Email Resource settings (`b9dbb6ee55`,
`7507c3fbf7`). Form, Project Emails, and self-hosting docs now describe
conditional Cloud `EMAIL_SERVICE` delivery, explicit missing-service
failure, and self-hosted integration requirements (`61fcf3f3d5`);
published staging behavior remains unverified.
- [x] Document Cloud Email attachment count and encoded content/request
limits in Form docs (`61fcf3f3d5`).
- [x] Document provider/quota errors and the Cloud email hosting
requirement. Builder Form, Project Emails, and self-hosting docs cover
new-Form visitor behavior and hosting limits. Sending-domain setup and
companion SaaS deployment are tracked in the companion SaaS PR.
- [x] Refresh the current bundle-version metadata in all eight static
project fixtures and synced user metadata in seven;
`packages/protocol/src/fixtures.test.ts` passes 17/17 locally and
Linux/Windows fixture CI both pass on head `70a2091c69`.
- [x] Complete relevant local checks on current PR work. Focused tests,
typecheck, lint, formatting, and diff checks pass locally; full GitHub
CI passes on `84f24e8d12`.
- [x] Reviewed each new-Form issue row against current code/tests; no
additional legacy compatibility beyond the explicitly tracked
#5512/#3891 route/query fixes is a closure condition. Evidence: #3023
file and attachment checks in
`packages/sdk/src/managed-form-email.test.ts` and
`packages/cli/src/prebuild.test.ts`; #5512 and #3891 page parameters and
all query values in `packages/cli/src/prebuild.test.ts`; #5432 repeated
multipart values/files in the same generated-route suite; #5499 parallel
destinations and five-recipient preflight in
`packages/sdk/src/managed-form-handler.test.ts`,
`managed-form-resources-generator.test.ts`, and
`managed-form-submission.test.ts`; #3530 redirect behavior in
`packages/sdk-components-react/src/native-form.browser.test.tsx`; #5428
trusted browser info headers in `packages/cli/src/prebuild.test.ts`;
#5658 sender/reply-to validation in
`packages/sdk/src/managed-form-email.test.ts` and the companion SaaS
Worker tests; #6487 feedback scrolling in
`packages/sdk-components-react/src/form-feedback-scroll.browser.test.tsx`;
#4414 and #4385 project defaults, overrides, and recipient isolation in
`packages/sdk/src/email-resource.test.ts` and
`apps/builder/app/builder/features/settings-panel/resource-panel.test.ts`;
#5018 unique submission references and retry behavior in
`packages/sdk/src/managed-form-submission.ts` and
`managed-form-email.test.ts`. These are merge-time issue mappings, not
production delivery evidence.

### Acceptance criteria

- The insert menu offers one new Form. Deprecated old components and
their migration are outside acceptance; no legacy compatibility path is
added.
- Staging end-to-end coverage exercises an empty Form blocked from
navigation and a configured Form submitting to its Resource
destinations, plus two HTTP Resources, two Email Resources, files,
confirmation, and partial failure through Builder → publish → submit.
- Form, Resource, Project Emails, and hosting docs match the released
behavior, including the Cloud email requirement. Generated contracts,
focused checks, full CI, and staging tests pass before the PR is ready.

## Batch 8 — Submit current-draft Forms from Builder Preview

- [x] Add an authenticated Builder server endpoint for Preview
submissions. It checks the Builder session and exact project build
permission before effects; route tests cover authorized and rejected
requests.
- [x] Load current draft Form configuration and invoke the shared SDK
runtime handler without a prior publish or draft/published contract
comparison; Preview route and graph tests cover changed drafts.
- [x] Build the draft Resource graph with publish-equivalent scope,
dependency, and defaults rules. Reuse the existing
`evaluateExpressionSync` implementation for supported expressions; do
not add QuickJS, WASM, AST, or another expression engine. Focused graph
and expression tests cover parity.
- [x] Reuse the shared SDK managed-form handler in Builder Preview and
generated published-site routes. Their request, auth, and configuration
entry points remain runtime-specific; SDK and generated-route tests
cover both paths.
- [x] Execute HTTP Resource Actions from the Builder server in Preview,
and from the published-site Worker after publish. The Email Service
handles email only; Preview route tests assert the outbound HTTP
boundary.
- [x] Route Preview Email Resources server-to-server through the Email
Service with a server-only credential; published-site Workers use the
private `EMAIL_SERVICE` binding. Builder checks user and project
permission first; tests cover the Email Service call contract and
rejected auth.
- [x] Removed the unapproved Preview warning that submissions send real
emails and webhook requests; new user-facing warning copy requires
explicit approval.
- [x] Node tests execute the shared handler locally with a stubbed Email
Service boundary, without Cloudflare infrastructure. Staging
published-Form Email delivery is verified in Batch 7; the production
Worker limiter check is tracked in companion SaaS PR #666.
- [x] Keep a permanent Builder Preview transport Playwright E2E that
submits a current draft through the Preview backend and verifies the
response without publishing or triggering email/webhook side effects
(`apps/builder/e2e/tests/preview-managed-form.[shard-2].e2e.ts`; focused
2/2).
- [x] Preview converts rejected `onManagedSubmit` callbacks into a
structured 500 failure, clears stale result and pending state, and shows
the current error. Browser regression covers a prior structured failure
followed by a rejected callback
(`packages/sdk-components-react/src/native-form.browser.test.tsx`). The
new Form also cancels native submission when its `onSubmit` callback
throws; a Chromium browser regression clicks a submitter with
`formAction`/`formMethod` overrides and verifies cancellation, no
Resource dispatch, and visible failure (`28f7a93eed`).

### Acceptance criteria

- A Form in an unpublished project can be submitted from Builder Preview
without publishing first.
- The Preview endpoint rejects unauthenticated users and users without
edit access to that project before any HTTP request or email send
occurs.
- Preview uses the current draft. Changes to controls, Actions, Resource
dependencies, effective email settings, visitor-addressed Email
Resources, or redirects can be tested immediately without republishing
or a published-contract comparison.
- Draft expression results match published Resource evaluation for
supported syntax using the existing `evaluateExpressionSync`
implementation; no second expression engine is introduced.
- HTTP Resource requests go directly from the Builder server to their
configured destinations. Email Resource requests go through the central
Email Service Worker, which rejects requests without the server
credential; that credential never appears in browser traffic.
- Results, errors, uploads, response bindings, feedback, and redirects
match published submission behavior while keeping the Builder open.
- The generated published-site Worker continues to use the same shared
handler and public visitor submission path, and calls Email Service
through `EMAIL_SERVICE`.
- Local Node E2E tests exercise the shared handler and Builder Preview
authorization without Cloudflare; a separate integration check verifies
the real Email Service contract.

## Production release boundary

**The production Email Service Worker was deployed at version
`8769175b-15d2-4c09-a312-b78dc93fd2f3` under the user's explicit request
to update the code and Worker to use `TRPC_SERVER_API_TOKEN`. An
authenticated malformed validation request returned `400
invalid_request` before send; that request sent no email.** This Builder
PR makes no deployment to the live site; live-user Form submissions are
outside its verification scope.

## Boundaries and known tradeoffs

- No old-component migration, submission storage or history,
account-wide email quota feature, CC/BCC, custom From address or
customer sending-domain verification, HTML email, preview text, or
separate branding editor. Builder Preview submission is included in
Batch 8 and runs against the current draft without requiring
publication; it is a real submission, not a separate email test-send
feature. Cloudflare account sending quotas still govern delivery;
migrating Plunk authentication or other Plunk operations is separate
follow-up work.
- The Discord requests for an explicit CAPTCHA challenge, direct Google
Sheets connection, multi-thumb/vertical slider, and an exact submission
copy to an unverified visitor are not covered here. A visitor-addressed
Email Resource always gets the fixed platform preamble and site URL; its
optional builder-configured body may use normal Resource bindings.
- A timeout retry can duplicate a delivery that succeeded remotely. A
visitor-addressed Email Resource can send to an unverified submitted
address; the native Email Service limit of 50 recipient checks per
project UUID per Cloudflare location per minute can reject legitimate
local bursts; it is approximate and not a global cap.
- The five team-delivery cap is a server preflight across the whole
Form; it is intentionally not calculated across independent Resources in
the Builder.
- Project defaults apply site-wide. Form-only expressions resolve only
in Resources scoped inside that Form.

## Additional Builder and Resource behavior

### Builder UI

- [x] Add the `formState` bullet to the Action tooltip after the exact
user-facing description is approved; verify it with the focused tooltip
test. The exact wording was approved; covered by focused Action tooltip
tests.
> Sends each form submission to all selected destinations at the same
time.
  > - formData — submitted fields from this Form.
  > - browserInfo — visitor details.
  > - formState — current Form state: initial, success, or error.
  > - results — each action’s response, in order.
  > - errors — failed actions and their messages.
- [x] In the Variables list and Edit variable preview, show
server-provided `browserInfo` after an authenticated Builder Preview
submission when available. Use only trusted `cf-connecting-ip`, ignore
spoofed `x-forwarded-for`, and sanitize Referer by removing query and
fragment before exposing it. Keep `previewBrowserInfo` private to
Builder memory and strip it before Canvas pubsub. Route, bridge,
Variables list, and Edit variable preview tests cover these paths;
focused preview tests pass (2/2) and bridge tests pass (7/7). Published
Form behavior stays unchanged.
- [x] Recognize supported Chromium browsers without requiring
`window.chrome`; user-agent detection and unsupported-browser regression
coverage are committed.
- [x] Add the approved “Clear all” button below multiple visible toasts
and dismiss the stack; shared toast implementation and browser
regression are committed.

- [x] Remove the background from the maximize button in code and
expression editors; keep the icon button transparent, consistent with
other editor controls. `code-editor-base.browser.test.tsx` verifies
transparent hover styling.

- [x] Implement separate tooltips for the Form variables `formData`,
`browserInfo`, `formState`, `results`, and `errors`; exact wording was
approved on 2026-10-08, and `variables-section.test.tsx` verifies the
displayed descriptions.

- [x] Add a description to the Action label tooltip and show a yellow
warning triangle with “Add at least one action” when the Action list is
empty. The warning disappears once an Action is added; covered by the
Form Action control browser test.

- [x] Reset the Form Preview runtime state to `initial` when leaving
Preview, so the Builder returns from success/error feedback to the
editable Form state without changing saved bindings or published
behavior. Add regression tests.

- [x] Route every managed Form submission error through the user-styled
Error Message instance. Make its message dynamic from the submission
error result, remove static fallback text and raw React alerts, and
cover validation, partial Action failure, and service errors.

- [x] When a Form, Form content, Success Message, or Error Message
instance is selected on the canvas, show that selected instance in
Builder regardless of its bound `show` value. This is an editor
selection override only; preserve the saved binding and runtime
behavior. Add regression tests for each state.
- [x] Add the approved description “Sends each form submission to all
selected destinations at the same time” and a multiline list for
`formData`, `browserInfo`, `formState`, `results`, and `errors` to the
Action label tooltip; use lowercase “action” in descriptions. Remove the
aggregate `status` variable; Form state remains
`initial`/`success`/`error`, and each Action’s HTTP status stays in its
result/error entry. Retain the approved “Add at least one action”
warning when no Action is selected. The Action reset button says
“Reset.” Focused insertion and Action UI tests cover these behaviors.

- [x] The Action plus-button tooltip says “Add a resource to submit this
Form”; the focused Form Action control browser test covers it.

- [x] Rename the Form’s component prop from `submission` to `action`,
matching the singular **Action** control. Store selected Actions as an
ordered array of Resource references with enabled state. Execute enabled
requests in parallel while preserving Action order in `results` and
`errors`. Update generated metadata, runtime handling, and tests; do not
add legacy compatibility.

- [x] Add Delete, Copy, and Paste to each variable row menu and its
right-click context menu. Within Variables, right-click anywhere allows
Paste; target-specific commands are disabled without a variable row.
Remove the Variables section header menu. The Edit variable dialog menu
has Delete, Copy, and Refresh only; remove its redundant quick-copy
button and do not offer Paste there. Serialize definitions as versioned,
namespaced JSON; omit internal IDs, create fresh IDs on paste, preserve
and remap expression references, and report unresolved references. Base
menu and transfer tests pass.

- [x] Rename user-facing Builder UI section and control labels from
`Data variables` to `Variables`; retain `CSS variables` for clarity.
Update UI tests.

- [x] Give variable labels a tooltip that shows defining scope,
type/value-kind details, and Delete with its shortcut. Option-click
deletes the local variable; deleting a shadow reveals its ancestor. UI
tests cover the tooltip and Option-click deletion.
- [x] Clicking the rendered “Value comes from” source label navigates to
and focuses its defining instance. `variables-section.test.tsx`
exercises the real ancestor “Body” source label and Global root label;
the full focused file passes 9/9.
- [x] Label a Global Root user variable source as “Global root,” not
“System,” and keep the source label clickable to navigate to and focus
its definition. `variables-section.test.tsx` verifies the “Global root”
label, absence of “System,” and source navigation.
- [x] Fix the verified source-label behavior defect: clicking “Value
comes from” must reliably focus its defining Variables row after
ancestor or Global root navigation. Regression tests pass both
standalone and in-suite: `variables-section.test.tsx` passes 12/12, and
isolated ancestor/Global root navigation-focus cases pass 3/3 each.

- [x] Prevent deletion of the managed Form’s required `formState`,
`results`, and `errors` variables by stable identity in the Builder UI,
including after renaming, through Variables row menus, the Edit variable
menu, right-click context menus, and Option-click; ordinary user
variables remain deletable. `variable-menu.test.tsx` covers the menu
paths and ordinary deletion; focused suite passes 21/21. The
required-variable rule is enforced in both the Variables UI and the
mutation/domain layer.

- [x] Show a yellow warning triangle only when a variable name matches
an ancestor; allow the child-scope name. Tests cover an existing shadow
and an ancestor rename.

- [x] Keep the variable row highlighted while its Edit variable dialog
is open; use the shared list-row open state and cover it with a
regression test.

- [x] Use the shared warning color token for variable-shadow triangles
in both the Variables list and Edit variable dialog; the Builder UI test
verifies the token.

- [x] Reject duplicate Resource variable names on the same instance
while allowing a nested scope to shadow a parent name; add validation
and regression tests.

- [x] Use sentence case for the Form setting label: `Success redirect`.

- [x] Expose Form-scoped runtime values as inspectable JSON in Builder
Preview. `formData` reflects the current named controls and values
before submission, including empty values; `browserInfo` shows its safe
context values. Add focused UI and runtime tests.

- [x] Add `resourceName` alongside `resourceId` in each ordered
result/error and relevant inspection entry; verify naming, partial
failure, retries, and bindings without exposing secrets.

- [x] Independently rechecked #3891 against the implemented contract:
`system.search` returns a string for one value and an ordered array for
repeated query values. `packages/sdk/src/system-search.ts` and its
regressions cover repeated, empty, comma-containing, and encoded values;
Batch 2a retains the generated-route and documentation evidence.

- [x] Verify that a selected Email Resource defined on the Form, an
ancestor, or global scope uses the existing generated submitted-fields
default body when both its Resource body and Project Emails body are
unset. Preserve explicit Resource/Project body overrides, including an
explicitly empty Resource body, Visitor receipt behavior, and rejection
of explicit out-of-scope `formData` bindings. Draft and published graph
tests cover all three scopes and both override types in
`managed-form-resources-generator.test.ts` and
`managed-form-draft-graph.test.ts`; focused SDK tests pass (73 tests).

### Resource editor

- [x] In Project Settings → Emails, show all long explanations in the
existing info tooltips beside their related controls, preserving the
exact existing copy. Specifically verify the Recipients and Owner
plain-text body explanations; `section-emails.test.tsx` currently covers
tooltip content and keyboard access.

- [x] Restore the approved attachment behavior sentence in the Project
Settings → Emails documentation, in the Visitor email paragraph only:
“When a Form includes file inputs, submitted files are attached to the
outgoing Email Resource message by default. Turn off ‘Attach submitted
files’ to omit them.” Do not add this text to the settings UI. Added in
`c96fb7c409`; `pnpm check:generated-docs` and `git diff --check` pass.

- [x] Replace the outdated Visitor attachment statement in the Form
guide with the same approved sentence used in Project Settings
documentation: “When a Form includes file inputs, submitted files are
attached to the outgoing Email Resource message by default. Turn off
‘Attach submitted files’ to omit them.” Added in `40dd8c4af3`; `pnpm
check:generated-docs` and `git diff --check` pass.
- [x] **Email Resource Type description:** The exact description was
approved, and `variable-popover-type.test.tsx` asserts the Email option
displays “Send a plain-text email through Webstudio Cloud when a Form is
submitted.” Focused type-selector suite passes 7/7; Builder typecheck
passes.
- [x] **Method dropdown copy approval:** Exact copy is approved and
tested: GET “Read data from a server.”; POST “Send data to create or
process something.”; PUT “Replace data on a server.”; DELETE “Delete
data from a server.” Keep the existing Form-submission note with POST;
focused Resource editor tests verify all four descriptions and the POST
note.
- [x] Keep Email Resource field labels as `Sender`, `Recipients`,
`Subject`, and `Body`; use the existing binding control and Expression
Editor for supported values, and show validation errors in the standard
input tooltip rather than below the field. `resource-panel.test.ts`
asserts each label appears exactly once, covers the binding controls and
Sender/Custom recipient Expression Editor save/unmount/remount/reopen,
and verifies validation errors appear in standard input tooltips; the
focused suite passes 30/30.
- [x] Give Email Resource recipient controls programmatic accessible
names by associating the existing visible “Recipients” label with its
selector and using existing text for the Custom recipient input and
Visitor email selector. Keep visible copy unchanged.
`resource-panel.test.ts` passes 32/32; Builder typecheck, changed-file
`oxlint --deny-warnings`, and `oxfmt --check` pass.
- [x] Remove the separate Project default reset button; switching away
from Custom recipients clears its value. Show the custom address input
directly below the mode selector and the Visitor field selector without
redundant field labels. `resource-panel.test.ts` covers literal and
bound recipient clearing when switching to Project and Visitor modes,
mode-specific controls, and no duplicate Recipients label; the focused
suite passes 30/30, including Sender and Custom recipient bindings
through the Expression Editor and save/unmount/remount/reopen.

- [x] Verify and test the REST and GraphQL type-option descriptions
against the approved wording: “A REST resource is a configuration for
secure data fetching.” and “A GraphQL resource is a configuration for
secure data fetching with GraphQL.” Preserve the existing “You can
safely use secrets in any field.” note. `variable-popover-type.test.tsx`
covers both descriptions.
- [x] **Visitor email field info tooltip:** Approved exact copy for the
info tooltip beside the Visitor email field selector: “Adds a note with
this site’s URL to help recipients identify where the message came from
and discourage spam.” Keep it visible only when Visitor email field is
selected; verify the text opens on hover and keyboard focus and closes
on blur in `resource-panel.test.ts`.
- [x] Match the Edit variable dialog menu trigger icon size to the other
header controls and make the menu’s minimum width about twice its
current width; `variable-menu.test.tsx` covers the header icon and 360px
minimum width.
- [x] Simplify Builder Email Resource preflight: remove duplicated
Worker-authoritative mailbox and attachment metadata policy checks;
retain a small pre-Base64 attachment size guard, subject safety, and
aggregate recipient protections. Tests verify early size rejection,
valid attachment delivery, and Worker rejection propagation.

- [x] Replace the superficial Headers and Search params label
info-tooltip descriptions with clear technical explanations of their
purpose; propose and obtain approval for exact user-facing copy before
implementation. The approved label info icons explain these sections; no
additional plus-button tooltips are needed. Exact wording approved;
covered by focused Resource editor tests.

- [x] **Recipient selector descriptions:** Add the approved descriptions
exactly: Project recipients — “Send to the addresses in Project
Settings, or to the account holder if none are set.”; Custom recipients
— “Send to the addresses entered below.”; Visitor email field — “Choose
a Form input to use as the recipient’s email address.” Verify copy and
visibility in `resource-panel.test.ts`.
- [x] **Move Visitor email guidance into its recipient option
description:** Removed the extra info icon next to the selected Visitor
email field input. The Recipients dropdown option now explains that the
confirmation email goes to the selected Form input address.
`resource-panel.test.ts` passes (41/41); formatting, lint, and diff
checks pass. Pushed as `a85c77308f`.

- [x] Put the Form POST-method note in Method dropdown option
descriptions instead of inline copy; keyboard and pointer behavior are
covered by the Resource editor tests.
- [x] **Format option descriptions:** Approved exact copy: label
`Format`; help below the control “Choose how to send the body.”; `auto`
— “Uses text/plain for text values, application/json for other values,
and multipart/form-data when files are included.”; `application/json` —
“Sends an object or array as JSON.”; `multipart/form-data` — “Sends
fields as form data, including files.” `resource-panel.test.ts` verifies
the descriptions, below-control help, standalone GET hiding, Form Action
GET visibility, and Body editor; the focused suite passes 34/34.
- [x] **Headers and Search params add-button tooltips:** Superseded by
the user's clarification: the approved info icons beside the Headers and
Search params labels explain those sections, so no additional tooltips
on the plus buttons are needed. The existing info-icon descriptions
remain covered by the focused Resource editor tests.
- [x] **Project Settings → Emails helper-text placement:** Move the long
inline explanations (including Recipients and Owner plain-text body
help) into adjacent info tooltips so the settings stay compact. Do not
add or rewrite user-facing copy without explicit approval; preserve
approved behavior and cover tooltip placement with focused tests.
- [x] **Shadowing warning tooltip copy:** Reverted the unapproved
proposed wording and retained the existing tooltip.
`variables-section.test.tsx` verifies the parent-scope shadow condition,
including an existing shadow and an ancestor rename; a unique name
removes the warning.
- [x] Add an info tooltip beside Cache max age explaining the response
cache lifetime in seconds. A focused Resource editor test verifies it.
- [x] Use the approved info icons beside the Headers and Search params
labels to explain those sections; no additional plus-button tooltips are
needed. Use Minus icons to remove individual rows. Focused Resource
editor tests cover the label info tooltips and row removal.
- [x] Keep the Body label and remove the Body content type selector.
Infer text/plain for string bodies and application/json for objects,
arrays, numbers, and booleans; preserve automatic multipart handling for
files, explicit request-format validation, and useful editor/error
behavior.
- [x] Label the body-format dropdown Format and show the approved help
“Choose how to send the body.” below it. Use the effective method: hide
Body and Format for a standalone GET Resource; a GET-configured Resource
selected as a Form Action is effectively POST and keeps both controls
visible. Focused editor tests verify the help placement, visibility, and
selected body format.
- [x] Show Response, Request, and Diagnostics tabs for HTTP Resources,
with Response first and selected by default. Capture the actual outgoing
method, URL, headers, and body plus response status/status text,
headers, final URL when available, and body from the latest Form Preview
submission, including retry attempts. `request-inspector.test.tsx`
verifies the order and default selection, including the captured numeric
response status and actual response body for both HTTP and Email
exchanges (focused suite 7/7); route and inspector tests verify captured
exchange data, no replay or substitution from current config, and
authenticated Preview-only, no-store, ephemeral, bounded, redacted
capture. Files show metadata instead of bytes. Email inspection shows
the captured transport request and actual HTTP response, with
credentials and encoded attachment bytes redacted; the logical delivery
outcome remains separate for Form failure handling. Published staging
submission and attachment delivery are verified in Batch 7.

- [x] Classify Email inspection exchanges by Resource kind, never URL
alone; focused sanitizer and inspector tests verify that an ordinary
HTTP Resource targeting the Email Service URL keeps normal request-body
redaction.
- [x] Preserve actual Email Service response headers in the Email
inspection Response view; `resource-loader.test.ts` verifies transport
capture and `preview-form-inspection.server.test.ts` verifies safe
headers reach the inspection view.
- [x] Preserve the actual Email Service HTTP response body/status
separately from the logical Email delivery result, so provider-level
failures remain Form failures even when HTTP returns 200. Read the
response body once so inspection and delivery validation share the same
parse; keep captured data bounded. Add regressions for malformed success
and non-JSON error responses. Single response read, bounded inspection
snapshot, provider body and logical outcome regressions; SDK tests pass.

- [x] Keep the Variables panel context menu scoped to the panel:
right-clicking a variable row targets that row, blank panel space allows
Paste, and right-clicking inside the Edit variable dialog does not open
the panel menu. The dialog menu offers Delete, Copy, and Refresh only;
`variable-menu.test.tsx` covers row, blank-space, and dialog
context-menu behavior.
- [x] Keep Email Resource attachments enabled by default. The default
contact Form’s owner Action keeps attachments enabled, while its visitor
confirmation explicitly opts out because it does not include the
submitted file. `managed-form-email.test.ts`, `resource-panel.test.ts`,
and the default contact Form E2E cover defaults and opt-out.

- [x] Support bindings for Email Resource Sender and Custom recipients
using the existing binding/Expression Editor pattern; evaluate and
validate resolved mailbox values before dispatch.
`resource-panel.test.ts` verifies Sender and Custom recipient bindings
through the Expression Editor and save/unmount/remount/reopen (30/30).
`email-resource.test.ts` verifies resolution and rejects empty,
non-string, and invalid values; `managed-form-handler.test.ts` verifies
invalid bound values are rejected before Resource fetch or email send
(35/35 combined SDK tests).
- [x] Memoize Email Resource recipient expression scope inputs
(`recipientScope` and aliases) so editing a recipient value does not
restart asynchronous expression evaluation on every render; add focused
regression coverage. Unrelated Resource edit regression passes;
`resource-panel.test.ts` 34/34.
- [x] Make all Email binding tests use CodeMirror’s platform-aware Mod
shortcut (Meta on macOS, Control elsewhere), including Sender/Custom
recipient round-trip and Project-mode bound-recipient clearing, so they
pass Ubuntu CI. `resource-panel.test.ts` contains no hard-coded
Meta/Control keyboard chords and passes the full focused suite (30/30).
- [x] Ensure generated server modules import Email binding helpers only
once when both regular Resources and managed Forms use Sender or Custom
recipients bindings; generated-site resource-only and combined-module
tests in `packages/cli/src/prebuild.test.ts` assert one helper import
(2/2), and the CLI typecheck passes.
- [x] Ensure generated modules for generic (non-managed) Email Resources
using `senderExpression` or `recipientsExpression` import the runtime
resolver helpers. `packages/cli/src/prebuild.test.ts` verifies one
helper import and typechecks a mixed generated site with page-level and
managed-Form Email bindings, plus a page-level-only site; both focused
cases pass (2/2).
- [x] Enforce the five-recipient total before dispatch for Email
Resources with Sender or Custom recipients bindings. Count known/literal
recipients even when the Email Resource depends on another Resource;
reject recipient expressions whose count depends on a Resource output
before any send. Preserve Form-variable bindings.
`managed-form-handler.test.ts` covers a resolved six-mailbox binding and
verifies rejection before HTTP fetch or email send; existing dependency
tests cover Resource-output counts.
- [x] Prevent or support Email recipient expressions that depend on
another Resource. The editor currently accepts these expressions, but
managed-submission preflight rejects them; align editor validation and
runtime behavior, with focused regressions. Editor binding scope now
excludes Resource outputs; server preflight remains fail-closed; focused
tests and Builder typecheck pass.
- [x] Disallow Email recipient bindings that depend on another Resource
in the Builder editor, while keeping server preflight fail-closed.
Resolving dependencies before the recipient-limit check could trigger
arbitrary requests; add focused editor and preflight regressions. Editor
binding scope now excludes Resource outputs; server preflight remains
fail-closed; focused tests and Builder typecheck pass.
- [x] Fix Type and Recipients dropdowns in a narrow sidebar so each
dropdown panel matches its input trigger width, using Name as the
reference; focused tests cover Type, Recipients, and Method. Flattening
removes the separate Resource dropdown.
- [x] Flatten System resources into the single Type list: remove the
separate “System resource” category/dropdown and list Sitemap, Current
date, Assets, and Email alongside the other Type choices. When editing
an existing System Resource, changing between categories
(Sitemap/Current date/Assets ↔ Email) persists the selected type.
Preserve existing Assets Resources whose URL expressions use valid
non-JSON string-literal syntax; add a reopen regression in
`variable-popover-type.test.tsx`. Assets non-JSON URL expression reopen
regression passes; Builder popover tests pass.

- [x] Fix the Builder typecheck failures in `resource-panel.test.ts`
binding tests caused by nullable queried-button assertions, then rerun
and pass the Builder typecheck. Builder typecheck passes and
`resource-panel.test.ts` passes 30/30.
- [x] Add a generated published-route regression proving that an
unset/default Form Action body with an uploaded File selects multipart
automatically and preserves filename, MIME type, and bytes.
`packages/cli/src/prebuild.test.ts` runs the generated endpoint for both
defaults and React Router adapters; the focused test passes 2/2.
- [x] Add regression coverage that an ordinary Project/Custom Email
Resource with `includeAttachments` absent resolves default-on through
the Email Service payload. `packages/sdk/src/managed-form-email.test.ts`
covers both recipient modes and asserts attachment payloads; the focused
suite passes 29/29, and `pnpm oxfmt --check
packages/sdk/src/managed-form-email.test.ts` passes.

- [x] Resolve current changed-file lint findings (two `no-explicit-any`
findings in changed tests and one `react-hooks/exhaustive-deps` finding
in `variable-popover.tsx`), then rerun scoped lint. Changed-file `oxlint
--deny-warnings` passes on 50 TypeScript files; `oxfmt --check` passes
on all 48 matched changed files; Builder typecheck passes; focused
settings/variables suites pass 47/47.

- [x] Simplify `handleManagedFormSubmission` by removing the redundant
one-Visitor-Email validation and relying on the shared
`validateManagedFormRecipientLimit` check; add or retain focused
preflight regression coverage. Handler and submission suites pass 48
tests, and SDK typecheck passes.

## Additional Form and Builder behavior

- [x] **Use the existing upload icon for the File input component:** The
shared `UploadIcon` is used in the File Input template and asserted by
`core-templates.test.tsx`.

- [x] **Verify Success redirect in Builder Preview:** On successful
submission, keep navigation inside Preview for internal routes, resolve
relative URLs against the previewed page, and navigate the preview frame
for external URLs. Add end-to-end coverage for a matched internal route,
an unmatched route (which currently silently does nothing), and hash
navigation (which currently updates the hash without scrolling).
Preserve error feedback and never redirect after a failed action.
Matched internal, unmatched internal, and delayed hash target cases pass
in Preview E2E; failed actions retain error feedback and do not
redirect.

- [x] **Finish Email diagnostics loading:** The Email Resource
Diagnostics tab no longer remains pending without a request key; focused
regression coverage verifies the settled empty state.

- [x] **Separate variable editors by type:** The dialog composes focused
modules for HTTP, GraphQL, Email, Assets, Parameter, and scalar types;
shared controls remain neutral. Focused Builder tests and typecheck
pass.

## Issue checklist

Eleven issues below are intended to close when this PR merges; #5432 and
#4093 are related but remain open. Each closure row must be implemented
and verified first; keep remaining work unchecked.

| Issue | Required result before merge | Batch |
| --- | --- | --- |
| #3023 | Configure file inputs in the Builder; deliver single/multiple
files to HTTP destinations and Cloud email within documented limits. |
3, 5, 7 |
| #5512 | Current-page parameters work in new and legacy Form Resource
bindings, including after navigation. | 2a |
| #4093 | Related issue for the legacy Webhook Form; this PR implements
Resource Actions and response bindings in the new Form but does not
change legacy submission beyond the explicitly tracked #5512/#3891
route/query binding fixes, shared lifecycle, or CLI conversion behavior.
Keep #4093 open. | 2, 3, 6, 6b |
| #5432 | The new Form sends repeated field values as standard multipart
entries without losing selections. Legacy Webhook Form behavior remains
unchanged except for the separately tracked #5512/#3891 route/query
binding fixes; this PR does not close the original issue, and migration
is separate. | 2, 3 |
| #5499 | One submission can deliver to email and multiple HTTP
destinations together; document the ten-destination and
five-team-delivery limits. | 2, 5, 6 |
| #3530 | A successful submission follows the configured redirect;
failure does not redirect. | 6 |
| #5428 | A destination receives the trusted visitor IP, user agent, and
language through configured Resource headers, with setup documented. |
2, 7 |
| #5658 | Custom subject and Sender work as agreed, with provider-level
tests for From and Reply-To. | 4, 4a |
| #6487 | New Forms reveal off-screen feedback, leave visible feedback
alone, respect reduced motion, and do not scroll before redirecting. | 6
|
| #4414 | Editable name/reply address, subject, plain-text template, and
translated content work with project defaults and per-Resource
overrides. Webstudio remains the sender, as agreed. | 4, 4a, 5 |
| #4385 | Two forms in the same project can send to different configured
recipients without changing each other's delivery. | 4, 5 |
| #5018 | Separate new-Form submissions produce separate Gmail
conversations; retries retain the same submission reference. | 5a |
| #3891 | Every selected query value is available in new and legacy Form
Resource bindings through `system.search`, with a single value remaining
a string and repeated values becoming an ordered array. | 2a |

- [x] After PR #6530 merged (`a49a6769bd`), verified the new Form needs
no port: React Router and Remix use the same Form submit path, which
creates fresh `FormData` and calls `set` once for the bot field on each
request. `generates one fresh bot field for each managed submission`
passes (shared browser suite 7/7); Router managed Form tests pass 2/2,
Router refresh tests 7/7, and Remix refresh tests 5/5.

# Related issues

Closes #3023
Closes #5512
Related #4093
Related #5432
Closes #5499
Closes #3530
Closes #5428
Closes #5658
Closes #6487
Closes #4414
Closes #4385
Closes #5018
Closes #3891
Related #6520 — [Multi-step Forms with resumable local
drafts](https://github.com/webstudio-is/webstudio/issues/6520)

The existing Webhook Form bug #6437 and prior legacy-form fix #6436 are
outside this new-Form feature and are not claimed as closed here. The
old email planning PR #6486 is superseded by this description.

- [x] **Use the canonical server token for Preview email:** The user
confirmed the canonical `TRPC_SERVER_API_TOKEN` is supplied from
Infisical in Preview and Production. Builder Preview now sends this
token to Email Service `/v1/preview-send`; the redundant
`EMAIL_SERVICE_PREVIEW_TOKEN` wiring is removed. Focused route tests
verify authorization, missing-token behavior, webhook-only behavior, and
redaction.

- [x] **Defer validation for a new Resource URL:** Untouched blank URLs
are not marked invalid until save; edited invalid values validate on
blur. Regression tests pass.

- [x] **Handle safe public Resource redirects:** Public GET/HEAD
redirects are revalidated and allowed without forwarding credentials;
unsafe destinations and cross-origin body requests remain blocked.
Protected-fetch tests pass.

## Form component identity correction (2026-10-11)
- [x] Keep component picker labels and compatibility behavior clear:
show the new component as **Form (Beta)**, and keep the existing
**Webhook Form** available in its current Data category through the next
release. Update the guide for the 10-Action limit and Navigation mode
when Actions are empty. Builder component-list and registry template
tests pass.

- [x] Use one canonical managed component identity, `Form`, for Resource
submission and Navigation mode. Keep `WebhookForm` only as the legacy
component through the next release, and keep generic `Element` with tag
`form` for native browser behavior. Update exports, metadata, templates,
adapters, migrations, docs, CLI/MCP discovery, and E2E identifiers;
verify saved projects migrate without changing legacy Webhook Forms.
Project migration tests pass (25/25), and the CLI prebuild suite
verifies saved bundles are migrated before rendering and endpoint
generation.

## Framework-neutral Form core (2026-10-11)
- [x] Move managed Form contracts, browser submission behavior, and
server delivery into SDK Form entries. Keep React hooks/rendering in the
React adapter and router refresh timing in the router adapter; preserve
compatibility exports. Builder and CLI consume the dedicated server
entry. StrictMode, cancellation, redirect fallback, and refresh-failure
behavior have regression tests. SDK build and published types,
SDK/React/router/CLI/Builder typechecks, focused SDK/React/router/CLI
tests, generated route tests, and the browser bundle boundary check
pass. Astra Medium reviewed the architecture and final diff; no
remaining findings.

## SDK package boundaries (2026-10-11)
- [x] Document each SDK package’s architectural role and dependency
constraints. Added root READMEs for `sdk`, `sdk-cli`, and
`sdk-components-registry`; clarified the existing React SDK, React
component, Radix, Router, Remix, and animation package READMEs.
Formatting and diff checks pass.

## Form submission lifecycle
- [x] **Handle rejected submissions when the controller refuses them:**
Consume abort-aware submission promises when a duplicate or disposed
Form submission is refused. Regression tests cover both paths; four
lifecycle tests, SDK typecheck, and diff checks pass.












## Unified Canvas selection reveal (2026-10-12)
- [x] Reduce selection reveal metadata and resolver ceremony while
preserving occurrence validation, owner/source boundaries, and focused
coverage. Replaced ten one-element rule arrays with one optional rule
each, removed the unused fallback field and redundant source union,
combined selector validation with path construction, and bounded the
source search. The optimization removes 24 production lines and 10 test
lines (net), with the shared resolver and derived override layer intact.
Focused Builder tests pass 66/66, Radix 20/20, registry 1/1; SDK, Radix,
registry, and Builder typechecks, scoped lint, formatting, and diff
checks pass. The nested Radix portal check uses a rendered harness
rather than the full Canvas. Independent Astra Medium re-review found no
correctness findings. Pushed commit
`5bc392d3788ef004f9a35c9a12d82dab6087053a`; `builder-pr/whr.staging` SHA
verified.
- [x] Replace duplicated Radix Navigator selection handlers with
declarative component metadata and one Builder-owned, occurrence-aware
selection override resolver. Native Select, HeadSlot, and
AnimateChildren hooks remain for their separate editor behavior.
- [x] Reveal selected `ws:show=false` instances and ancestors through
resolved prop overrides, including Form success/error feedback, while
preserving normal published rendering.
- [x] Resolve fixed `open` and effective item/tab `value` rules for
Dialog, Popover, Tooltip, Select, Collapsible, Tabs, Accordion, and
Navigation Menu. DialogOverlay retains its existing reveal behavior.
Preserve unrelated memory props and Preview behavior.
- [x] Verify resolver, lifecycle, nested Dialog portal mounting,
Collection occurrence isolation, authored expression updates, and Canvas
prop behavior. Local focused Builder suites pass 66/66; Radix 20/20;
registry 2/2. SDK, Radix, registry, and Builder typechecks, scoped lint,
formatting, and diff checks pass. Independent Astra Medium review
findings were fixed and rechecked. Pushed commit
`f8bb1078195e17704ed856c4334683584e983485`; `builder-pr/whr.staging` SHA
verified. These are local automated checks, not live UI verification.





### Remaining Navigator hook migration

- [x] Migrated every remaining Navigator hook consumer: native
Option/Select and tagged `ws:element` use effective option values and
HTML text fallback; HeadSlot uses a fixed self override; AnimateChildren
uses a fixed override only for its exact selection.
- [x] Removed Navigator hook implementations,
`Hook`/`HookContext`/`InstanceData`/`InstancePath` and
`getClosestInstance`, hook subscriptions and store, library
registration, package hook entrypoints/build inputs, and the stale
catalog hook capability field.
- [x] Added behavior coverage for evaluated/bound native values,
empty/zero values, HTML whitespace/NBSP fallback, repeated occurrences,
nearest owner, stale selection, HeadSlot rendering, exact animation
selection, Preview, deselection, and no saved-prop/memory mutation.
Existing Radix runtime index behavior remains covered.
- [x] Completed Astra Medium self-review and independent Astra High
review. Fixed the High review finding: native fallback values must
preserve non-ASCII whitespace.
- [x] Final repository search found no remaining Navigator hook
consumers or registrations.

Validation:

- `pnpm --filter @webstudio-is/builder exec vitest run
app/shared/nano-states/selection-reveal.test.ts
app/shared/nano-states/selection-reveal-render.browser.test.tsx
app/shared/nano-states/components.test.tsx
app/shared/nano-states/props.test.ts
app/shared/nano-states/instance-selection.test.ts
app/canvas/instance-selection-events.test.ts
app/canvas/selected-instance-effects.test.ts` — 7 files, 84 tests
passed; the final resolver rerun passed 14 tests after adding one
additional authored-expression regression (85 tests across the same
files).
- `pnpm --filter @webstudio-is/project-build exec vitest run
src/runtime/component-catalog.test.ts
src/runtime/component-templates.test.ts` — 16 tests passed.
- `pnpm --filter @webstudio-is/sdk-components-react-radix test` — 20
tests passed.
- `pnpm --filter @webstudio-is/builder exec tsc --noEmit` — passed.
- `pnpm --filter @webstudio-is/sdk --filter @webstudio-is/react-sdk
--filter @webstudio-is/sdk-components-react --filter
@webstudio-is/sdk-components-animation --filter
@webstudio-is/sdk-components-registry --filter
@webstudio-is/project-build typecheck` — all 6 packages passed.
- `pnpm exec oxlint --deny-warnings` on all changed TypeScript files,
`pnpm exec oxfmt --check` on all changed files, and `git diff --check` —
passed.

Native HTML options use the browser's effective value semantics
(authored value or text); their runtime has no numeric index default.
Radix items continue using their existing runtime index fallback. All
three consumers migrated; no hook mechanism remains.
2026-10-14 20:03:07 +02:00
Oleg Isonen 3c7dac7eb0 fix: resolve MCP preview and content graph reports (#6511)
## Summary

- Raise the document-reference graph budget independently of the 20-file
content hydration limit, and document the 2,000-document graph cap. This
lets the reported 29-post referenced query complete.
- Make iterative preview startup verify the generated project identity
endpoint instead of compiling the application route; exact route
validation remains with screenshot/capture.
- Keep production preview readiness tied to the application route even
when no CSS assets are present.
- Include a stable structured diagnostic when the browser DevTools
connection closes, while preserving the existing reconnect behavior.
- Document and test a safe leaves-first `convert-instance` migration for
required-part composite trees.

Closes #6508, #6515
Ref #6509, #6510

## Verification

- Content Engine suite: 656 tests passed.
- Vision suite: 85 tests passed.
- CLI preview/readiness/controller tests: 32 passed; project-build
instance conversion tests: 89 passed.
- CLI typecheck, targeted lint, formatting, and diff checks passed.
- The full CLI suite timed out in unrelated launcher/fixture tests under
parallel load. The preview-script and launcher tests passed with fewer
workers, and the MDX fixture test passed alone in 29 seconds. No full
local agent evaluation command was run.
- All required GitHub checks passed on latest commit `7f7b981` (29
successful; visual regression skipped).

## Remaining scope

- #6509 remains open: this PR improves diagnostics but does not
establish a stable Edge DevTools capture.
- #6510 remains open: this PR removes the initial route-compilation
dependency, but does not yet provide route-specific preparing/retry
status.

## Checklist

- [x] Review the change and preserve production preview route checks.
- [x] Add regression coverage for the review fix and required-part
conversion recipe.
- [x] Update MCP manual docs with the supported conversion order.
- [x] Run focused tests, typecheck, lint, and formatting.
- [ ] Run the full local agent evaluation suite (not run).
- [x] Confirm required CI checks pass on the latest commit.
2026-10-09 16:14:18 +02:00
Oleg Isonen 00fcb26afe fix: align path rules with published routes and document headers (#6488)
## Problem

Builder page navigation, Authentication, and HTTP response headers used
different path matchers from published Remix pages. For example, Builder
did not select a `/docs/*` page at `/docs`, and a request for `/%64ocs`
could reach the `/docs` page without matching its Authentication rule.

## Change

- Use Remix's framework-independent router for page selection and
project path rules. Adapt Webstudio named splats and preserve encoded
static path segments.
- Keep Authentication's first matching rule and Headers' ordered
application of every matching rule.
- Preserve previously saved encoded literals such as `/%2A`, `/%3F`, and
`/%3Aid`. They match only their literal encoded pathname and cannot
become wildcards or parameters. Previously accepted malformed encoding
remains a literal path.
- Replace Builder's separate route ranking and URLPattern matcher.
URLPattern remains for page path validation and path editing.
- Use the request URL for the project-domain authentication exemption
instead of trusting `x-forwarded-host`. Generated HTML, XML, and text
routes share one authentication helper.
- Extend the HTTP headers guide and shared path pattern reference with
matching behavior, an iframe example, and verification steps.

## Validation

- Authentication package: 88 tests. New regression cases cover saved
Authentication and Headers rules, literal matching, route ranking,
parameter values, and a marker collision attempt. The regressions failed
before the fix and pass now.
- Project build: 2,484 tests, including 214 route and redirect checks.
SDK: 636 tests. CLI: 1,165 tests. Builder system: 5 tests.
- Typechecks: Authentication, project build, Builder, SDK, and CLI.
Lint, package boundaries, formatting, and diff checks pass.
- The fixture pipeline completed and made no additional changes. The
generated fixture files already changed by this PR remain included.
- Staging project build 196 published successfully using the companion
published-site Worker branch. The edge dispatcher was deployed from its
staging branch to normalize forwarded hosts. Live HTTP checks covered
base and child routes, dynamic pages, redirects, 404s, global headers,
and route-scoped headers. The generated site challenged protected base
and child paths, accepted the route password, and left public routes
accessible. The project domain intentionally skips route-specific auth
and has a separate staging Basic Auth gate, so the successful
route-password request was checked in the generated production runtime.
A forged `x-forwarded-host` no longer changes the hosted project-domain
auth decision.
- An initial CLI run alongside two other full suites timed out in one
fixture API test; that test passed alone, and the full CLI suite passed
when rerun alone.

## Compatibility

- [x] Preserve previously accepted percent-encoded route literals before
merging.
- The old URLPattern test asserting that `/docs/*` does not match
`/docs` was intentionally changed because the published Remix router
does match the base path. Tests for the removed custom route sorter were
replaced with router-selection tests.

## Rollout

- [x] Migrate matching in this repository.
- [x] Update user documentation.
- [x] Verify package consumers and related code.
- [ ] Merge
[webstudio-saas#665](https://github.com/webstudio-is/webstudio-saas/pull/665)
before releasing this PR. The current published-site Worker template
imports the removed `matchesPathnamePattern` export; without the
companion change, a published site fails at Wrangler build.
- [ ] After publishing the new `@webstudio-is/wsauth` version, update
the published-site Worker template’s pinned `0.301.0` dependency, remove
the temporary fallback, and deploy the hosted Worker.

Replaces #6482 so the review tracks the deployed `headers.staging`
branch.
2026-10-02 13:51:39 +02:00
Oleg Isonen a6a9049399 fix: validate published MDX before publish (#6468)
## Related issue

Related to #3550.

## Summary
- Run the existing published-MDX materializer during pre-publish
validation and surface its error diagnostics before starting dynamic or
static publishing.
- Reuse the dependency walk that builds the publication artifact to
limit validation to reachable Content Blocks, including blocks whose
asset is missing.
- Add actionable fix hints for unavailable MDX assets and invalid HTML
nesting.
- Show a compact error/warning count in the publish dialog, with a
details dialog that has counted Errors and Warnings tabs and hides empty
tabs.
- Include relevant IDs and navigation links in reports when available,
with guidance when they are not; allow users to copy all reports and
select the text.
- Resolve missing-document IDs against loaded asset metadata and show
the filename and Content Assets folder path when available.

The page-tree pre-publish audit already checks HTML content models, but
it cannot see content generated from MDX assets. This extends preflight
to that content using the same materializer and diagnostics as the
publish build. It also catches unavailable MDX assets before the publish
job starts.

## Plan and status

### Implemented
- [x] Reuse existing publish-time MDX discovery and materialization so
preflight checks the content that will be published.
- [x] Collect every page-tree audit error and warning, not only the
first finding for each severity.
- [x] Collect every MDX publish error and provide a location and
remediation hint.
- [x] Collect missing or ambiguous template warnings and
content-database omission warnings.
- [x] Show a compact error/warning total in the publish dialog.
- [x] Add a details dialog with standard panel padding, spaced and
separated findings, and counted Errors and Warnings tabs; hide empty
tabs.
- [x] Copy all reports from the dialog header and allow report text
selection.
- [x] Include diagnostic codes and relevant instance, asset, document,
and resource IDs in reports.
- [x] Keep “See issues” visible in the narrow publish panel.
- [x] Keep element navigation available from page-tree audit findings
and link content findings when the target is available.
- [x] Use the same findings UI for dynamic and static publishing.
- [x] Block publishing on errors and allow warnings to proceed.
- [x] Keep unexpected preflight failures visible to the user.
- [x] Show a preflight failure either in the open publish dialog or in a
toast when the dialog is closed, avoiding duplicate reports.
- [x] Replace opaque “Document … could not be loaded” errors with
actionable guidance, including the asset filename and folder path when
available.

- [x] Load linked document sources during MDX validation and prebuild
when they are absent from the compact content artifact.

### Regression checks completed
- [x] No-findings validation preserves the normal publish path.
- [x] Single and multiple findings show accurate totals and details.
- [x] The “See issues” action is visible in the publish panel and opens
diagnostics.
- [x] “Show element” navigation remains wired to the affected element
and closes the publish dialog.
- [x] More than five MDX errors and more than ten missing-template
warnings remain available in diagnostics.
- [x] Mixed MDX errors, audit errors, template warnings, and database
warnings are combined and counted.
- [x] Errors stop dynamic and static publishing; warnings do not block
publishing.
- [x] Error-only, warning-only, and mixed states show the right tabs and
counts, with empty tabs hidden.
- [x] Copy-all includes every error and warning with context, and report
text is selectable.
- [x] An open dialog receives inline preflight failures without a toast;
a closed dialog receives a toast without a duplicate inline report.
- [x] Missing-document errors include filename/folder context when
available and useful fallback guidance when metadata is unavailable.

- [x] Reproduce the linked-author failure with the affected project’s
hosted build, then confirm it builds successfully with the fix.

## Verification
- `pnpm --filter @webstudio-is/project-build test --
src/runtime/published-mdx.test.ts` (26 tests passed)
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm --filter webstudio typecheck`
- Affected project: `webstudio build --template react-router` reproduced
the document load error before the fix and completed after it.
- `pnpm --filter @webstudio-is/builder test --
app/builder/features/publish/publish-validation-results.test.tsx
app/builder/features/publish/content-database-publish-warning.test.tsx
app/builder/features/publish/publish-error.test.ts
app/services/build-router.server.test.ts` (26 tests passed)
- `pnpm --filter @webstudio-is/builder typecheck`
- `pnpm exec oxfmt --check` on changed files
- `git diff --check`
- Reviewed dynamic/static preflight and element-navigation code paths.
2026-09-29 18:27:20 +02:00
Oleg Isonen 6044792b77 docs: Explain Radix force-mount animation requirements (#6471)
## Summary

Document the animation constraints around Radix `forceMount`: closed
content remains mounted and needs explicit closed-state CSS, while Radix
suppresses CSS transitions and animations during height measurement, so
the first opening may not animate.

Updated Accordion and Collapsible docs, custom prop descriptions, and
generated prop metadata so the guidance appears in component discovery.

Closes #6197.

## Checks

- [x] Accordion and Collapsible documentation updated
- [x] Generated prop metadata refreshed
- [x] Package typecheck
- [x] Targeted Collapsible tests
2026-09-29 16:00:41 +02:00
Oleg Isonen 8a46350c9a fix: Compile MDX Content Blocks in hosted builds (#6455)
Hosted MDX pages can publish successfully and then fail to render
because the publish runner skips their source files. Restore source
downloads and compilation for MDX Content Blocks, including dynamic
article queries and template dependencies.

Reuse the existing publication planner and local content compiler.
Markdown-only pages keep their bodies on demand; hosted MDX pages
download article sources and pay the necessary compilation cost in the
runner, not the publish API. Reject missing content databases and MDX
materialization errors instead of deploying an incomplete page.

Ref #6450
Ref #6454

## Checklist

- [x] Reproduce missing MDX content using the affected deployed build.
- [x] Restore hosted MDX compilation without duplicating the
static-export compiler.
- [x] Add a sync → compile → generated query → React server-render
regression with a metadata-only API bundle and a frontmatter-bound
Content Block.
- [x] Verify new regression checks fail without the required behavior
and pass with it.
- [x] Reject missing MDX sources and content databases; preserve remote
article bodies when no MDX Content Blocks need compilation.
- [x] Review documentation impact and update the Content Engine
publishing explanation.
- [x] Run CLI tests, typecheck, lint, formatting, and package-boundary
checks.
- [x] Run the full fixture link/sync/build pipeline; no generated
changes.
- [x] Rebuild the affected project locally and exercise its generated
production request handler: HTTP 200 with the article title and body.
Asset requests were served from synced local files.

The live project was not modified or republished. Its existing
title/description bindings still use an array access for a single-result
query; that separate project-data issue is not changed here. Agent
evaluations were not run. Affected sites need republishing after the
updated CLI artifact is deployed.
2026-09-28 18:42:17 +02:00
Oleg Isonen 9b1100f25b fix: Restore hosted Assets queries (#6454)
Hosted Assets queries started returning 502 in 0.301.0 because publish
sync omitted their query database. Restore that database for hosted
builds while keeping Markdown body-reference queries on demand. Static
exports still prepare their database in the build runner.

Reject builds with Assets queries but no database before changing
generated files, so this failure cannot silently produce another broken
deployment or erase the previous generated output. Uses the existing
shared query compiler; no MCP-specific logic or API schema changes.

Closes #6450
Ref #6427

Replaces #6453 with the same commits on `assets.staging`, which matches
the staging deployment branch policy.

## Checklist

- [x] Reproduce the reported 502 and verify the fix on project and
custom origins.
- [x] Preserve static export behavior and on-demand Markdown loading.
- [x] Add a regression test for rejecting missing databases; demonstrate
failure before the fix and success afterward.
- [x] Review documentation impact and update the Content Engine
publishing explanation.
- [x] Run relevant tests, Builder/CLI typechecks, lint, formatting, and
package-boundary checks.
- [x] Run the full fixture link, sync, and build pipeline against the
patched preview API. Regenerate all eight JSON snapshots with the
current formatter; parsed data is unchanged and generated application
files are unchanged.

Validation: 88 CLI tests, 22 Builder tests, and four no-body-read
publication tests pass. Fixture size checks were unavailable without
compiled app bundles. Agent evaluations were not run. Existing affected
sites need republishing after the fix is deployed.
2026-09-27 00:16:04 +02:00
Oleg Isonen d78e770d82 perf: Keep article indexing out of publish API requests (#6427)
Ref #6416

The publish workflow was doing too much work in one short-lived API
request. This moves content indexing into the build runner and keeps
article bodies out of hosted/SSR sync.

## What changed

- The build-bundle API can return the lightweight project bundle without
preparing a full content index.
- The CLI downloads required assets directly from storage and builds the
index locally for SSG. Asset downloads remain bounded to 10 concurrent
requests.
- Hosted/SSR sync skips Markdown and MDX article downloads and does not
compile an article index. It still downloads regular assets needed by
the build.
- Markdown body queries use stored frontmatter and fetch only the
requested article at runtime. MDX still needs source downloads and
compilation for SSG.
- Public MCP and content-engine docs explain the Markdown/MDX tradeoff.

This removes article parsing and article-body transfer from the tRPC
request. SSG still downloads article sources from storage and compiles
MDX in the build runner. Hosted runtime article reads remain on demand.

## Checklist

- [x] Keep the tRPC bundle request limited to project metadata and
necessary records.
- [x] Prepare SSG content indexes in the build runner; keep hosted
article bodies out of sync.
- [x] Cover no-body query preparation, direct asset reads, and
SSG-versus-hosted sync behavior with regression tests.
- [x] Update public CLI, MCP, and content-engine guidance.
- [x] Regenerate and verify fixtures.
- [x] Run workspace tests, typechecks, lint, formatting,
package-boundary, generated API/docs, and fixture-size checks.
- [ ] Verify the deployed publish flow has comfortable margin below the
hosted API timeout.

## Verification

- `pnpm -r --workspace-concurrency=1 test`: passed, including all 3,082
Builder tests and 1,150 CLI tests.
- `pnpm -r typecheck`, `pnpm lint`, `pnpm check:package-boundaries`,
`pnpm check:generated-api`, `pnpm check:generated-docs`, `pnpm
check:fixture-sizes`, and `pnpm exec oxfmt --check`: passed.
- The new hosted article-download regression test was confirmed to fail
with the old behavior and pass after the fix.
- A deployed end-to-end publish timing check is still needed, so this PR
remains draft.
2026-09-26 11:56:37 +02:00
Oleg Isonen a4c3b37fc2 fix: Unblock reported MCP workflows and improve issue diagnostics (#6438)
## What this fixes

MCP authors can reuse an existing design token in JSX fragments, create
valid border shorthands with CSS variables, and bypass a font that never
settles during responsive screenshots. Common fixed page titles with
spaced separators are also accepted. Issue reports now include the
project ID and safe, bounded IDs from the failed operation, plus clearer
response and browser-startup diagnostics.

## Scope and tradeoffs

- `token("name")` attaches an existing design token; an unknown name
returns a not-found error. Empty token definitions from Builder/UI
remain definitions and follow the normal conflict rules.
- Border shorthands map a single unresolved `var()` to the remaining
width, style, or color field when the other fields are explicit.
- `waitForFonts: false` skips font readiness but still waits for stable
layout frames. The default remains unchanged.
- Fixed titles such as `Pricing - Plans` stay literal. Addition such as
`first + second` remains an expression. Wrap ambiguous subtraction in
parentheses, for example `(first - second)`. Other ambiguous title forms
may still need an exact report.
- Only recognized operation-level IDs are attached to a public GitHub
issue. Authored values, raw arguments, URLs, and credentials are
excluded. IDs from later successful diagnostic reads are not captured.
- This does not fix the underlying response-transform failure; it
improves future reports for that case.

Closes #6424
Closes #6425
Closes #6434
Closes #6439
Ref #6435
Ref #6430
Ref #6417

## Verification

- [x] Reproduced the privacy, page-expression, empty-token, and
content-mode token-reference regressions with failing tests, then
confirmed the fixes pass. A regression test also covers parenthesized
subtraction.
- [x] Full protocol, template, vision, project-build, and CLI test
suites pass; focused Builder issue-report and fragment tests pass. The
protocol, CLI, and project-build suites were rerun after the CI fix.
- [x] Relevant package typechecks, lint, package-boundary check, fixture
pipeline, generated API check, and generated docs check pass. Fixture
output has no diff.
- [x] Reviewed the authoritative MCP documentation and regenerated the
API metadata and MCP docs.
- [x] CSS parser regressions cover border, each side shorthand, and
unresolved variables in width, style, and color positions; css-data
tests, typecheck, and lint pass.
- [x] Fixed the local evaluation runner to pass the task and Webstudio
MCP tools to Luna/low. Each of the six fixtures passed at least once;
focused MCP/evaluator tests, CLI and project-build typechecks, lint, and
formatting pass.
- [x] The complete Luna/low suite passed all six fixtures in two
consecutive sequential runs. Baselines remain incompatible with this
model/effort override and were not updated.

## Follow-up

- If another fixed-title form still fails, use the new project-level
report context and the exact title shape to narrow the parser case.
2026-09-25 21:01:43 +02:00
Oleg Isonen 2c9fd16689 feat: configure custom response headers by route (#6429)
## Description

Project Settings starts with an empty response-header rule list. Users
can add valid HTTP response headers for all paths (`/*`) or an
Authentication-style route such as `/private/*`, and remove any
configured rule. The Headers and Authentication views share the
accessible rule-list form. The header form suggests standard
response-header names and common values for the selected header, while
accepting custom names and values. `Set-Cookie` is not suggested because
a rule can store only one value per header and would replace existing
cookies.

`X-Powered-By`, `X-Content-Type-Options`, and
`Strict-Transport-Security` are dispatcher-owned and cannot be
configured. Users may configure `Content-Security-Policy`,
`X-Frame-Options`, and `Referrer-Policy`; when absent, the dispatcher
supplies their defaults. The SDK validates header names, values, routes,
duplicates, and size limits. The CLI emits only configured rules.

Customization requires Pro for custom-domain publishing, while unchanged
defaults and staging remain available on Free plans. The server checks
the saved production snapshot against the project owner's plan.

Companion Cloud worker PR:
https://github.com/webstudio-is/webstudio-saas/pull/664. Deploy its
dispatcher support before exposing these settings in the Builder. Other
hosts need an adapter that applies the generated rules; Webstudio Cloud
dispatcher defaults do not apply automatically elsewhere. Static exports
do not apply headers automatically.

## Validation

- SDK, CLI, Builder browser, publish-warning, and production-build tests
pass.
- Builder, SDK, and CLI typechecks and lint pass.
- The companion Cloud worker tests, typecheck, and lint pass.

Replaces https://github.com/webstudio-is/webstudio/pull/6428 to use
`headers.staging`.
Closes https://github.com/webstudio-is/webstudio/issues/4748
2026-09-25 16:46:35 +02:00
Oleg Isonen 3d0a0657aa feat: Support async resource dependencies (#6030)
## Summary

Make `Resource<Document>` the lazy document-resolution abstraction
shared by Content Engine graphs, generated sites, and Builder previews.
Expressions use one async computation interface: referenced resource
documents resolve first, independent requests run concurrently, and
unused resources stay unloaded.

Ref #6029
Ref #6031

## Implementation checklist

- [x] Define one resource contract for synchronous or asynchronous
document resolution.
- [x] Resolve explicit roots lazily with memoization, bounded
concurrency, cancellation, and structured errors.
- [x] Preserve cycle, duplicate, and missing-resource diagnostics.
- [x] Extract resource dependencies with the existing expression AST.
- [x] Discover consumed resource roots from the current page tree and
page metadata.
- [x] Generate a lazy request graph for published page data, including
independent requests.
- [x] Resolve dependency documents before evaluating dependent request
expressions.
- [x] Integrate dependency-first loading with Builder preview caching
and invalidation.
- [x] Expose safe Resource bindings in the editor and reject
self/indirect cycles.
- [x] Adapt REST, GraphQL, Assets, date, and sitemap requests through
the same Resource contract.
- [x] Cover laziness, chains, shared dependencies, concurrency,
invalidation, cancellation, failures, and cycles with fail/pass tests.
- [x] Review documentation impact and document dependent Resources in
the Data variables guide.
- [x] Track collection-item and component-parameter-dependent resources
as follow-up #6031.
- [x] Regenerate and review affected fixtures.
- [x] Resolve action-resource request dependencies when an action is
invoked.
- [x] Reproduce and fix cached form submissions and multi-root
dependency-cycle hangs.
- [x] Document uncached form actions and regenerate affected fixtures.
- [x] Preserve single and multi-selected content until a cut
successfully writes to the clipboard.
- [x] Preserve boolean expression values in the search-exclusion
checkbox.
- [x] Cancel cut deletion if project data changes while preparing or
writing the clipboard; cover content/property edits and project switches
for single and multi-selection, and document the retry behavior.

- [x] Validate pending cuts after the final await, immediately before
deletion, and reproduce the queued-update race for single and
multi-selection.

## Technical direction

The persisted, revisioned `DocumentGraph` remains the Content Engine
artifact. `Resource<Document>` is the execution abstraction for
obtaining a document; its resolver evaluates only the dependency closure
reachable from requested roots. Static and remote documents therefore
differ by Resource implementation, not by expression value type.

Generated pages always emit a `ResourceRequestGraph`. Each node owns its
output name, dependencies, and request factory. Roots come from resource
variables actually referenced by the current page's metadata, props,
actions, or expression children. Unused and off-page resources remain
unresolved. The loader still accepts legacy request maps for
compatibility.

Builder preview uses the same page-root discovery as published
generation. Its existing cache remains the lifecycle boundary: cached
dependency documents unlock dependent requests, while invalidation
removes downstream requests until their inputs resolve again. The
separate canvas frame recomputes its local plan from the synchronized
cache so rendered expressions receive those documents.

Action requests and their dependencies resolve when a form is submitted.
Each submission sends a new action request, bypassing Webstudio's
resource cache. Dependency requests still use their configured cache
lifetime.

## Review simplifications

- Consolidated page resource-root discovery in the SDK and reused it in
Builder.
- Scoped root and action discovery to the current page tree.
- Removed the redundant immediate-resource helper; `Resource.resolve`
already accepts `Document | Promise<Document>`.
- Moved output names onto graph nodes and removed the duplicate
graph-level map.
- Removed unused planner output and corrected transitive cycle discovery
to include only Resource data sources.
- Regenerated fixtures so independent resources also use lazy graph
execution.
- Reused one abort-aware concurrency limiter for document sessions and
Resource resolution.
- Collapsed duplicate generator action scans and removed redundant
generated-resource bookkeeping.
- Unified legacy request maps and request graphs through one resolution
path.
- Shared the 20-request concurrency limit between SDK, Builder preview,
and the server batch boundary.
- Preserved dependency failure paths and original causes, including the
first cancellation reason.
- Removed invalid-request logging that could expose Resource headers or
request bodies.
- Made editor cycle detection alias-safe by comparing underlying
Resource IDs.
- Memoized page settings values to prevent repeated async validation
renders.

## Verification

Latest local verification for `bbca6395e3`:

- Both queued-update regression cases failed before the fix and passed
afterward. Validation now runs synchronously immediately before
deletion, after the final await, in both cut paths.
- Passed: 255 copy/paste and command tests, Builder typecheck, lint,
package boundaries, formatting, and diff checks.
- Reviewed documentation: the existing copy/paste safety description
remains accurate. Fixture inputs and outputs are unaffected; no fixture
regeneration was needed. Agent evaluations were not run because separate
approval is required.
- CI for the new commit is pending.

Previous local verification for `ffbc75b152`:

- Six regression cases failed before the fix and passed afterward:
content edits, property edits, and project switches during a pending
cut, with both single and multiple selections.
- Passed: 253 copy/paste and command tests, Builder typecheck, lint,
package boundaries, source formatting, and diff checks.
- An unchanged content-block test file initially failed to import in the
browser. Its isolated rerun and the full 253-test rerun passed without
code changes.
- The guard conservatively treats any project-data change as a reason to
keep the clipboard copy without deleting content. It shows a retry
message. Copy/paste documentation now describes this behavior.
- Fixture inputs and outputs are unaffected; fixtures were not
regenerated. Agent evaluations were not run because separate approval is
required. CI for the new commit is pending.

Previous local verification for `3eb580697c`:

- Reproduced both regressions with failing tests before fixing them:
premature deletion during cut and false search exclusion appearing
checked.
- Passed: 203 copy/paste and page-settings tests, Builder typecheck,
lint, package boundaries, formatting, and diff checks.
- Cut coverage includes keyboard and programmatic entry points, single
and multiple selections, delayed successful writes, rejected writes, and
disabled copying.
- Reviewed documentation impact: these fixes restore existing behavior;
no documentation update is needed. Only Builder UI and clipboard files
changed; fixture inputs and outputs are unaffected, so fixtures were not
regenerated for this commit.
- CI status for this older commit was recorded as pending at handoff.
Agent evaluations were not run; they require separate approval.

Previous local verification for `858b348ad`, rebased onto `cf606ca4c`:

- Regression tests failed before each fix and passed afterward: repeated
submissions in both generated route templates, plus three
multi-root/branching cycle cases.
- Passed: 590 SDK tests, 1,129 CLI tests, 72 focused Builder tests,
SDK/CLI/Builder typechecks, lint, package boundaries, and fixture
link/sync/build.
- Initial concurrent runs hit timeouts. The complete CLI suite passed
with one worker; the focused Builder suites passed without file
parallelism.
- Documentation and generated fixture changes reviewed. Agent
evaluations were not run; they require separate approval.
- CI for `858b348ad` passed: Main (including all six E2E shards), Visual
Regression, Lint PR, and Check submodules.

Earlier verification (before this update):

TDD fail/pass evidence includes resolver creation, AST dependency
discovery, published graph reachability, dependency-gated request
generation, Builder dependency-first planning, Resource editor cycle
filtering, page-tree root scoping, off-page action isolation, transitive
Resource-only cycle discovery, abort/timeout precedence, server batch
limits, and the rebased SSG fixture consuming its lazy Resource.

- `pnpm checks` passed after rebasing onto `ce84ddcf12`; it covered
workspace tests, typechecks, lint, package boundaries, generated
API/docs validation, and fixture link/sync/build.
- Earlier focused checks passed: Builder typecheck, 12
page-settings/pages unit tests, lint, formatting, and `git diff
--check`.
- Local generated-resource E2E passed for HTTP, GraphQL, and
current-date resources (3 tests).
- The page-actions and Marketplace page-settings persistence E2Es both
pass locally.
- CI for `6b8b08a70` passed: Main workflow (including all six Builder
E2E shards), Visual Regression, Lint PR, and Check submodules.
- Agent evaluations were not run because they require separate explicit
approval.

## Current behavior and limitations

- A failed dependency blocks its dependents and reports the resource
path and cause.
- One published resolution call is the memoization boundary; Builder
maps dependency readiness onto its versioned request-cache lifecycle.
- Scoped collection-item and component-parameter resources remain in
#6031.
- Builder skips cyclic dependency paths without blocking independent
resources.
2026-09-22 21:04:42 +02:00
Oleg Isonen 804cd39bc2 fix: Stop slow diagnostics from blocking publishing and clarify publish status (#6416)
Closes #6406

## What changes

Publishing no longer waits for advisory content-database and MDX
diagnostics. Those checks run alongside the publish request, so a slow
diagnostic does not prevent publishing from starting.

Permissions, pending content saves, and blocking project checks still
run before publishing. The server continues to enforce publish
authorization and build integrity.

### Publish dialog

- Add a green **Validate** button beside the blue **Publish** button,
with equal widths.
- Show waiting states and disable the actions while their work is
running.
- Let users validate without publishing. Successful validation shows a
persistent “Validation passed. Ready to publish.” toast.
- Clear the button's **Validated** state when the popover closes.
Reopening still reflects an active publish job.
- Move **Add new domain**, **Export**, and **Publish settings** into the
header's ellipsis menu.
- Show yellow progress icons for publishing domains, green checkmarks
for completed publishes, red icons for failures, and neutral icons for
unpublished domains. Built-in and custom domains share the status
mapping.

### CLI and MCP

Add `check-publish-readiness` to check the saved project without
starting a publish. It checks permissions, rejects empty or unusable
domain selections, reuses the existing build-integrity checks, and
returns content diagnostics. Broken resource references no longer
produce a false `valid: true` result.

### Content checks

Run independent collection reads with bounded concurrency instead of
processing every collection sequentially. This reduces serial waiting,
but does not eliminate the underlying storage reads or content
processing.

## Checklist

- [x] Keep authorization and blocking project checks in place.
- [x] Run advisory content diagnostics alongside publishing.
- [x] Add standalone validation, waiting states, and popover-local
validation results.
- [x] Consolidate secondary actions in the publish menu.
- [x] Distinguish pending, completed, failed, and unpublished domain
states.
- [x] Add CLI/MCP readiness checks using existing integrity and
verified-domain logic.
- [x] Bound concurrent collection reads.
- [x] Add regression tests, including invalid domains, missing
resources, status transitions, and popover state.
- [x] Demonstrate the latest regression tests failing without the fixes
and passing with them.
- [x] Review documentation impact: update CLI operation guidance and
metadata, and document domain status colors in the publishing guide.
- [x] Run focused Builder, CLI, and asset-uploader tests, Builder
typechecking, lint, formatting, and the generated-API check.

## Verification and limits

The latest UI check passed all 49 publish tests. The preceding readiness
fix passed 98 focused Builder tests and 130 CLI tests; collection
coverage passed 112 tests. Typechecking, lint, formatting, and
generated-API checks passed. The latest fixes do not affect fixture
inputs or generated site output.

Storybook screenshots and agent evaluations were not run because
approval was not given. A live production publish was not performed
during the latest verification.

Publish can finish before advisory diagnostics do. Returned warnings
appear afterward and cannot stop a publish that has already started.
Standalone validation still waits for diagnostics, and actual publishing
still requires content synchronization, server checks, and
build/deployment work. This does not promise that every publishing
timeout is eliminated.
2026-09-22 17:34:14 +02:00
Oleg Isonen e7181fffdc feat: Add GitHub-style Alert component (#6279)
## Summary

- Add one official `Alert` component with a content-editable `variant`
property for note, tip, important, warning, and caution.
- Register Alert in the component catalog with a generated title
descendant and Storybook story.
- Render GitHub-style alerts in Markdown Embed using the same DOM,
class, role, and variant contract.
- Materialize connected Content Block MDX alerts as actual Alert
instances and serialize content or variant edits back to `> [!TYPE]`.
- Document the component and both Markdown workflows.

## Implementation

Alert labels and variants live in Content Engine and are shared across
the component, Markdown renderer, and MDX adapters.

1. The Alert React component owns the wrapper, generated title, stable
classes, `role="note"`, and `data-variant`.
2. Markdown Embed transforms candidate micromark HTML before the
existing sanitizer and emits the same markup contract. It remains
sanitized HTML rather than a Builder instance tree.
3. Content Engine retains alert metadata on parsed Markdown blockquotes.
Project Build maps those nodes to Alert instances, reconciles their
authored children, and restores canonical GitHub syntax during MDX
serialization.
4. The component registry exports one Alert insertion template with a
styleable title descendant.

Unsupported, lowercase, misplaced, and malformed markers remain ordinary
blockquotes. There are no schema, migration, persisted-data, or
sanitizer-policy changes.

## Checklist

- [x] Add Alert as an official component with one Variant property.
- [x] Support all five GitHub alert types.
- [x] Share alert metadata and marker recognition.
- [x] Preserve nested Markdown and ordinary blockquote behavior.
- [x] Keep URL and HTML sanitization in the existing security boundary.
- [x] Add shared, title, and type-specific Markdown Embed descendants.
- [x] Materialize Content Block alerts as Alert instances.
- [x] Preserve alert content and variant changes through MDX
serialization.
- [x] Regenerate component props and Storybook artifacts from source.
- [x] Review documentation impact and update Alert, Markdown Embed,
Content Block, and navigation docs.
- [x] Verify fixture impact; no fixture inputs or generated fixture
outputs are changed.

## Verification

- [x] New tests demonstrated failure before implementation and pass
afterward.
- [x] `pnpm --filter @webstudio-is/content-engine test` — 417 passed.
- [x] `pnpm --filter @webstudio-is/project-build test` — 2,282 passed.
- [x] `pnpm --filter @webstudio-is/sdk-components-react test` — 144
passed, 1 skipped.
- [x] SDK component registry tests.
- [x] Relevant package typechecks, declaration builds, and production
builds.
- [x] Root lint, formatting, package-boundary, generated-doc, and diff
checks.
- [x] GitHub Actions main, E2E, lint, submodule, deployment, and
approved visual-regression checks.

## Known limitations

Alert identifiers must be uppercase and appear at the start of the
blockquote's first paragraph, matching the documented syntax. Markdown
Embed emits the component's markup contract but cannot create Builder
instances inside sanitized HTML.

Closes #6278
2026-09-18 00:42:50 +02:00
Oleg Isonen ea17eaa3c4 docs: explain the complete Content Engine workflow (#6403)
## What changed

- Add the complete Content Engine video tutorial to the main guide.
- Explain the launch benefits before the step-by-step setup: file-based
content, visual MDX editing, designed templates, collection entry forms,
and MCP automation.
- Document collection entry creation and editing in Content mode.
- Explain content collection folders in the Assets guide.
- Document that shared Slots cannot be used inside Content Block
Templates.
- Add missing links between Content Engine and Content Block
documentation.

## Release documentation audit

Reviewed the shipped MDX-backed Content Block, MDX template, structured
collection entry, collection authoring, query diagnostic, publication
warning, and asset-path changes. The detailed feature docs already cover
their implementation-specific behavior. This PR fills the remaining
discovery and workflow gaps across the main Content Engine, Assets,
Modes, and Content Block pages.

## Todo

- [x] Add the launch video to the Content Engine guide.
- [x] Update the main launch value proposition and supported scale.
- [x] Document collection workflows in Assets and Content mode.
- [x] Document the shared-Slot limitation for Content Block Templates.
- [x] Review documentation impact and navigation.
- [x] Verify generated documentation remains clean.
- [x] Verify changed relative links resolve.

## Verification

- Content Engine documentation tests: 2 passed
- Generated Content Engine and MCP documentation checks: passed
- Documentation TypeScript check: passed
- Relative link check: passed
- Git diff whitespace check: passed

The repository formatter excludes these GitBook Markdown files, so the
pre-commit formatting task had no eligible targets. No fixture inputs or
generated fixture outputs are affected.
2026-09-17 10:24:49 +02:00
Oleg Isonen f23007457d fix(content-engine): normalize asset path filters (#6389)
## Description

Fixes Assets path filters so authors can use visible folder and file
names while canonical stored paths remain URL-encoded.

- normalizes path filter operands segment by segment without double
encoding
- preserves already encoded filters and literal string matching behavior
- covers equality, inequality, membership, contains, starts-with, and
ends-with operators with spaces and URL-significant characters
- verifies local/Preview database execution and published runtime return
the same result
- documents visible path input in the generated Content Engine reference

Closes #6388

## Steps for reproduction

1. Create an Assets folder named `Tutorial posts`.
2. Add an MDX file inside it.
3. Create an Assets system resource with `path starts with "Tutorial
posts/"`.
4. Preview the query and expect the MDX file to match.
5. Change the value to `Tutorial%20posts/` and expect the same result
for backward compatibility.

## Regression proof

Before the implementation, the focused reproduction failed because the
matcher returned no items: expected `["alpha"]`, received `[]`. With the
fix, the expanded path-operator regression suite passes.

## Verification

- [x] Content Engine test suite: 47 files, 577 tests passed
- [x] Content Engine typecheck
- [x] CLI typecheck
- [x] Repository lint with warnings denied
- [x] Formatting check for every changed source/test file
- [x] Content Engine documentation test
- [x] Content Engine and MCP documentation generation is idempotent
- [x] Webstudio Features fixture generation is idempotent and typechecks
- [x] No agent evaluations were run

## Documentation impact

Updated the authoritative CLI Content Engine reference source and
regenerated the Builder Content Engine and MCP documentation. The new
guidance tells users to enter visible path names and notes that existing
encoded filters remain supported.

## Code Review

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

## Before requesting a review

- [x] made a self-review
- [x] added inline comments where behavior is not obvious

## Before merging

- [ ] tested on preview environment (preview dev login: 0000)
- [ ] updated test cases document (not needed; automated regression and
authoritative user docs were updated)
- [x] added tests
- [x] no new environment variables
2026-09-15 19:01:27 +02:00
Oleg Isonen 5d34b77fac feat: Improve collection entry authoring (#6361)
Collection authors and editors get clearer field help, direct entry
navigation, and actionable validation instead of warnings that point at
the wrong place.

Closes #6360

## Implementation

- [x] Keep resettable help open after a click while the label remains
hovered
- [x] Stop repeating the property label inside resettable help
- [x] Store JSON Schema field descriptions and show them in entry forms
- [x] Add an optional dynamic collection entry page for explicit canvas
navigation
- [x] Rename entry file actions to **Edit file**
- [x] Add **Open on canvas** to entry menus and Entry settings
- [x] Put the entry filename in the Entry settings title
- [x] Make collection validation banners explain the problem and open
the first affected entry
- [x] Remove false output-only `UNOBSERVED_FIELD` warnings while keeping
filter and sort diagnostics
- [x] Encourage useful field descriptions in general MCP collection
guidance
- [x] Update Content Engine documentation
- [x] Confirm no database migration is required
- [x] Regenerate the affected asset-query runtime fixture
- [ ] Visually review the affected Storybook states in light and dark
themes
- [ ] Run the complete local agent evaluation suite
- [x] Run broader package and Builder checks
- [x] Complete final diff review

## Technical notes

- `entryPageId` lives in `collection.json` under `x-webstudio`; no
database schema changes are needed.
- Entry creation still leaves the current canvas unchanged. Canvas
navigation only happens after the user selects **Open on canvas**.
- An entry page must contain exactly one URL parameter. The entry
filename supplies that parameter, so the page can identify the selected
entry.
- Output projection fields are schemaless and may be produced after
reference resolution, so catalog absence is no longer reported there.
Filter and sort fields still report catalog diagnostics.
- The structured-query runtime change affects the generated
`webstudio-features` fixture, which is regenerated in this PR.

## Verification

- 561 Content Engine tests
- 2,413 Project Build tests
- 253 Design System tests
- 1,794 Builder tests
- 67 focused Builder regression tests after the final navigation change
- 11 ResettableLabel browser tests
- 128 focused MCP tests
- Builder, Content Engine, Design System, and Project Build typechecks
- Repository lint and package-boundary checks
- Generated documentation checks
- Full workspace fixture build with `pnpm -r fixtures:build`
- Formatting and diff checks
- New regression tests were demonstrated failing before their fixes and
passing afterward

## Documentation impact

- [x] Updated the Content Engine guide for field descriptions, dynamic
entry pages, actionable validation, **Edit file**, and **Open on
canvas**
- [x] Updated MCP guidance so collection fields include editor-facing
descriptions
2026-09-11 19:25:04 +02:00
Oleg Isonen e69a3d2f8e feat: Create structured MDX entries from collection folders (#6317)
## Paragraph editing follow-up — 42a16db02c (September 9)

Nested formatted paragraphs now retain their text, links, styling
identity, and existing labels during editing and saving. No database
migration.

- [x] Review all four changed text-editor files.
- [x] Preserve text before and after nested links inside emphasis,
strong text, and spans.
- [x] Preserve existing instance labels on save and keep inherited
formatting from overwriting a link's styling identifier.
- [x] Demonstrate regression failures without each fix, then pass all 41
focused text-editor and MDX synchronization tests.
- [x] Pass Builder typecheck, lint, formatting, and diff checks.
- [x] Verify the reported paragraph in Design and Content modes: edits
reach MDX and survive reload; original text restored. Verify matching
link color and underline inside and outside editing.
- [x] Review documentation and fixture impact: this restores existing
documented behavior; no fixture inputs, generated output, or
documentation changes are needed.
- [x] Commit and push the reviewed fixes.
- [ ] Confirm CI on this commit before merging.

Full E2E and agent evaluations were not rerun for this focused
follow-up. Earlier CI and suite results below apply to their recorded
commits, not this new head.

---

## Final verification — 50093ed2b7 (September 9)

Article edits now retain inline formatting and save newly inserted
custom components reliably. Saving a file no longer sends a second
project-sync write that could restore an older revision. No database
migration.

- [x] Fix iframe-blur saves, inherited template defaults, unused
resolved assets, and stale template tag metadata.
- [x] Remove duplicate server writes for already saved file revisions;
share the result with the canvas using the existing external-content
sync channel.
- [x] Demonstrate new regression tests fail without their fixes and pass
with them. Keep E2E file-write, content-order, and reload assertions;
remove the obsolete duplicate metadata-write wait.
- [x] Pass all 2,955 Builder tests, all 2,409 Project Build tests, and
all 8 focused MDX E2E checks.
- [x] Pass Builder and Project Build typechecks, lint, formatting, and
diff checks.
- [x] Run the complete fixture pipeline with no generated changes. The
later Builder-only save-acknowledgment changes affect no fixture inputs
or outputs.
- [x] Verify live article fields, hero replacement/save/reload/restore,
Accordion text edits, and YouTube URL edits. Verify UI edits reach MDX
and MDX edits persist after reload. Restore original article content.
- [x] Review documentation impact: these fixes restore the documented
Content Block editing behavior; no further documentation change is
needed.
- [x] Apply `visual-change-approved` after the user's explicit approval
of the 24 reviewed intended visual changes.
- [x] Commit and push the reviewed changes; worktree is clean.
- [x] Confirm CI and mergeability on this final commit: all 19 Main
workflow jobs passed, including all six E2E shards; PR lint and
submodule checks passed. Visual approval run 34383497017 passed. Its
predecessor reported the 24 intended differences; all 24 current images
were byte-identical to the reviewed report, with zero capture errors.
GitHub reports no merge conflict.

Agent evaluations were explicitly skipped for these final runtime fixes,
as requested. Earlier evaluation results below apply to their recorded
commits, not this final commit. This summary supersedes historical
pending-check notes below. The PR has not been merged.

---

## Final merge check — 9bc31ca679 (September 9)

- [x] Merge main without rewriting branch history. Preserve main's
image-picker fix and description-list validation.
- [x] Reset inherited HTML parent context at template boundaries. All
four new regressions fail without the fix and pass with it.
- [x] Fix shared navigation style composition and alignment; verify the
browser regression fails before and passes after. Review focused
screenshots.
- [x] Pass 2,408 Project Build tests, 251 design-system tests, and 3
image-picker tests; Builder, Project Build, and design-system
typechecks; lint, formatting, and diff checks.
- [x] Run the complete fixture pipeline with no generated changes.
- [x] Rerun all six agent evaluation scenarios on the merged code with
GPT-5.5. All functional checks pass; incompatible/missing token-baseline
comparison remains explicitly disabled.
- [x] Review documentation impact: this merge preserves documented
behavior; the image-editing guidance was already updated in f90e39cfcc.
- [x] Commit and push; worktree is clean and GitHub reports no merge
conflict.
- [x] Confirm Main CI on 9bc31ca679: all 19 jobs passed, including all
six E2E shards.
- [x] Review final visual artifact 10110845203 from run 34367911826: 24
added/changed stories, zero capture errors. Reviewed collection dialogs
and icons, light/dark login gradients, dashboard and project-settings
navigation, panel spacing, button variants, inset lists, and resettable
labels. Fourteen captures are byte-identical to the previously inspected
CI images. Updated navigation and collection-settings captures were
inspected; the formerly blank story renders normally. Pixel measurements
confirm the columns story retains its intended column positions (second
column x=261, third around x=388).
- [x] Apply visual approval after renewed explicit user approval.
Final-head CI is tracked above.

The live hero replacement and article/custom-component checks below are
now complete. The PR has not been merged.

---

## Merge-readiness follow-up — September 9

- [x] Remove contradictory image guidance and add an executable
writable-source/read-only-alt binding recipe. Regenerate CLI/MCP docs.
- [x] Verify the recipe regression fails with the old guidance and
passes with the new guidance. All 10 focused documentation tests, 2
content-reference tests, CLI/docs typechecks, lint, formatting, and
generated-documentation checks passed.
- [x] Rerun all six agent evaluation scenarios with GPT-5.5: all
functional checks passed. The incompatible/missing token-baseline gate
was explicitly disabled; existing baselines are unchanged.
- [x] Commit the documentation follow-up as f90e39cfcc.
- [x] Review existing CI screenshots and identify unintended shared-list
alignment changes. Implement a local shared-style composition/alignment
fix with a regression test demonstrated failing before and passing
after; all 251 design-system tests and its typecheck passed.
- [x] Visually verify and push the shared list fix in 9bc31ca679. The
focused run compared 28 stories; navigation is left aligned, column
layouts remain intact, and the previously blank collection-settings
story rendered normally. No product fix was inferred from that one blank
capture.
- [x] Review and approve the intended visual differences. See the
current final-head CI status above.
- [x] Verify these changes affect no fixture inputs or generated fixture
output. No database migration.

The previously pending live hero-swap and article/custom-component
checks are now complete. See the current verification summary above.

---

## Article editing follow-up — d307c8ba3d (September 9)

The target blog's hero source was incorrectly marked read-only. It now
uses the existing writable image picker. Reading time and the author URL
also use direct writable values. Time properties now have native
date/date-time controls. No database migration.

- [x] Fix the target's hero source, reading-time structure, and author
URL bindings while preserving the current content and link destination.
- [x] Verify Content-mode category, title, excerpt, date, reading-time,
shared image Description, author name/URL, MDX heading, and list-item
edits reach their source files. Check reloads and restore test values.
- [x] Add date-only, local timestamp, UTC timestamp, empty-value, and
legacy-value regression coverage; demonstrate failure without the date
control and passing results with it.
- [x] Fix the macOS Bash runner's empty-argument failure. Demonstrate
default invocation and argument-forwarding regressions fail without
their behavior and pass afterward.
- [x] Update authoritative Content Block/Time docs and generated CLI/MCP
guidance: inventory every article field, use direct writable bindings
and appropriate controls, check saved source and reload, and never infer
full editability from one heading.
- [x] Pass 291 date/text/Time/design-system tests (one existing skip), 2
runner tests, and 9 documentation tests; relevant typechecks, lint,
package boundaries, formatting, and diff checks.
- [x] Run the complete fixture pipeline: no generated fixture changes.
The later runner/docs-only edits do not affect fixture inputs or
outputs.
- [x] Run the full Builder E2E suite: 82 passed. This used an explicit
reporter; the default invocation correction was then covered by the
runner tests.
- [x] Run all six agent evaluation scenarios on gpt-5.5 after the final
image guidance correction. All functional checks passed. The older-model
token-baseline gate was disabled explicitly; accepted baselines were not
changed.
- [x] Review, commit, and push code and generated documentation.
- [x] With explicit approval, replace the live hero image through
Content mode, verify the MDX reference and reload, then restore and
verify the original image.
- [x] Complete the article-field checks listed above. Insert Accordion
and YouTube from templates; verify UI edits reach MDX and MDX edits
persist after reload. Restore the original article content.
- [x] Inspect CI for 7834d90a61: formatting, lint, typechecks, generated
docs, package/Builder tests, build checks, and five E2E shards passed.
The long MDX lifecycle exhausted its total 120-second budget at its
final assertion; the trace showed completed edits and reloads, not a
stalled save.
- [x] Give only that lifecycle a 180-second total budget; leave
individual save waits and assertions unchanged. Both local repeats
passed (plus setup). Push d307c8ba3d; this test-timeout-only follow-up
affects no fixture inputs or generated output.
- [x] Verify Main CI for d307c8ba3d: all 19 jobs passed, including all
six E2E shards. Visual approval remains separate.
- [x] Finish visual review and approval after correcting navigation
alignment and verifying the collection-settings story renders. The user
approved the reviewed 24 intended differences.
- [x] Recheck target publication preparation: seven documents included,
zero omitted, zero missing-template omissions. No deployment.
- Deferred: manual Storybook/screenshots; not authorized for this pass.

These results supersede the earlier pending image-product decisions and
live-check notes. Evaluation status for the latest fixes is recorded
above.

---

## Blog authoring fixes — 6359f979a3 (September 9)

This review fixes failed UI-to-MDX saves, missing source Undo/Redo,
editor recovery and focus, and publication preparation for the migrated
blog. No database migration.

- [x] Stop treating template-record ordering as a style edit; still
reject actual style changes that cannot be saved in MDX.
- [x] Share save-preparation errors with property controls across the
canvas frame.
- [x] Keep malformed MDX as an unsaved draft, with repair and discard
paths; focus the source editor when opened from the Navigator.
- [x] Enable supported Content-mode Move actions using the existing
central permission rules.
- [x] Keep source Undo/Redo separate from transient rendering history.
Serialize rapid commands, wait for pending canvas edits, reject
stale-source conflicts, and retain the last 20 edits per project
session.
- [x] Fix publication candidate discovery for projected documents and
unrelated author references; keep rejecting unsupported references used
as the actual MDX source.
- [x] Demonstrate regression tests failing without their fixes and
passing afterward.
- [x] Run the complete Builder (2,938), Content Engine (559), and
Project Build (2,395) suites: 5,892 tests passed.
- [x] Pass relevant package typechecks, root lint, formatting, package
boundaries, and diff checks.
- [x] Run the full fixture pipeline; no generated fixture changes.
- [x] Review documentation impact and document draft recovery and source
Undo/Redo.
- [x] Verify live heading edits, author-file edits, malformed-draft
recovery, and immediate delete/Undo/Redo; restore test content and
confirm persisted source.
- [x] Verify target publication preparation: seven documents included,
zero omitted, no missing-template omissions. This did not deploy the
site.
- [x] Review, commit, and push.

### Remaining verification and decisions

- [x] Correct the target's read-only Image source binding. The existing
Content-mode picker replaces the article reference; shared Description
edits use Asset settings. No new image-editing API or data model is
needed. See the latest verification limits above.
- [x] Repeat the full E2E and agent evaluation suites after these
changes: 82 E2E tests and all six evaluation scenarios passed.
- [x] Repeat the live Accordion/YouTube insertion and editing flow after
the save fixes, including UI-to-MDX saves and MDX edits surviving
reload.
- Historical note: this round did not run CI or Storybook checks; later
results are recorded above.

Unknown JSX already has source-range lint diagnostics; it remains an
editable missing-template placeholder, not an unconditionally blocked
save. The target author's binding was also repaired through the UI so
Content mode can edit its referenced author file. Shared resolved URLs
remain read-only text; the image picker changes the article reference,
and Asset settings edits the shared Description.

This section supersedes earlier claims of complete verification for the
latest code; historical results below remain records of earlier runs.

---

## Sync and import verification — a2a567551c (September 9)

Ordinary `link` → `sync` → `import` now exports the current saved
Builder project with its current assets. It no longer mixes an older
published build with current files. Import uploads assets before
assigning their source folders, then restores those assignments when
importing the bundle.

- [x] Fix foldered asset imports without a database migration.
- [x] Preserve file formats, folder assignments, build metadata, and
stable repeat-sync output.
- [x] Keep explicit `--buildId` export behavior unchanged; default sync
does not require publishing.
- [x] Review implementation and demonstrate regression tests failing
before the fixes and passing afterward.
- [x] Update protocol test servers for the current Builder data
endpoint.
- [x] Review documentation impact and update CLI documentation and
README.
- [x] Run all 1,121 CLI tests and 43 HTTP-client tests, both package
typechecks, root lint, package boundaries, formatting, and diff checks.
- [x] Run the full fixture pipeline and include expected current-build
fixture changes.
- [x] Verify the real source through the local CLI: repeated sync gives
identical output; import into the supplied target succeeds. Blog listing
and article rendering were also checked during the import investigation.
- [x] Commit and push the fix.
- Deferred at user request: agent evaluations for this round. Later CI
results are recorded above.

Sync still downloads the project and missing asset files and builds the
content index locally. This is not a transfer-free operation. Default
sync exports editable-build metadata rather than published deployment
metadata; use the explicit build option when selecting a particular
build.

---

## Final review and verification — ab3a6b7c55 (September 9)

Existing entries now use the same field controls as New entry. The
author-name permission fix, field-level validation, publishing warnings,
and final cache fixes are included. No database migration.

### Completed

- [x] Review all 38 local files, including entry autosave, revision
conflicts, permissions, shared fields, collection discovery, and
publishing diagnostics.
- [x] Allow existing-folder setup without rewriting entries or rejecting
unconfigured frontmatter.
- [x] Add Entry settings with autosave, repairable invalid fields,
immutable filename slugs, and preserved MDX body/unknown properties.
- [x] Warn before publication when missing or ambiguous custom templates
omit content, including discovered dynamic sources.
- [x] Allow authorized frontmatter writes to referenced Markdown author
files and preserve error messages across the canvas-frame boundary.
- [x] Reject impossible integer limits, slugless generated-filename
patterns, and slug maximum length zero. Existing raw configurations
remain readable for repairs.
- [x] Share collection discovery across Assets, file editors, and
property controls. Builder first synchronizes project asset metadata
into the browser. The browser then groups that metadata once per update
and shares configuration/template reads, instead of repeating them for
each control. This does not remove initial synchronization or
entry-frontmatter reads. Preserve project/access isolation, refresh
retries, stale-response protection, and duplicate-filename invalidation.
- [x] Demonstrate new regressions fail before fixes and pass afterward.
- [x] Correct the folder-drag E2E's obsolete root-folder assumption and
verify remembered-folder persistence.
- [x] Review and update authoritative Content Engine and Content Block
documentation.
- [x] Merge current main and regenerate the conflicting CLI
documentation from its source.
- [x] Fix generated previews with shared development dependencies:
invoke React Router binaries through Node, isolate each preview's Vite
cache, and forward script options correctly through npm and pnpm.
- [x] Fix a false MDX conflict when a new edit arrives immediately after
the previous save is acknowledged. Advance the local revision before
asynchronous parsing and ignore stale refresh results.
- [x] Run the full fixture link/sync/build pipeline. Generated changes
contain only the expected React Router scripts and Vite cache location.
No database migration.
- [x] Review the final diff and scan added lines for credential
patterns.

### Verification results

- Builder: **2,934 tests passed**. CLI: **1,120 tests passed**.
- Content Engine: **559 passed**; Asset Uploader: **291 passed**;
Project Build: **2,394 passed**; Design system: **250 passed**.
- The clean full workspace run passed all 35 non-CLI packages. Its two
CLI failures exposed pnpm argument forwarding; the complete CLI rerun
passed after that fix. Earlier load-related test timeouts did not recur
in the clean run.
- All workspace typechecks passed. Builder and CLI typechecks and
repository lint were repeated after their final changes. Package
boundaries, generated API checks, and the complete
generated-documentation gate passed. Documentation was regenerated from
source; the post-commit clean-file check passed.
- Full Builder E2E: **82 passed** from a fresh build, including
generated previews, collection creation/reload, MDX editing, stale
revisions, and permission checks. The isolated disposable test database
was cleaned up afterward; development data was untouched.
- Full agent evaluations: **all six scenarios passed on gpt-5.5**,
including every functional check for Markdown blogs, reference
discovery, MDX article editing, authentication, fonts, and design input.
The account cannot use the old **gpt-5.4-mini** baseline model. Five
baseline comparisons are incompatible and the MDX scenario has no
accepted baseline; token-baseline gating was disabled explicitly.
Accepted baselines are unchanged.
- New preview and MDX regressions were demonstrated failing before their
fixes and passing afterward.
- Working-tree/diff checks passed. Main was merged without rewriting
branch history. CI was not checked, as requested.

### Final delivery checklist

- [x] Finish the full Builder E2E rerun.
- [x] Finish the full agent evaluation rerun with the authorized
accessible model.
- [x] Commit, verify the clean generated-documentation gate, and push.
GitHub reports the branch mergeable with current main.

No known code or functional-test blocker remains from this review. CI
was not checked at the user's request. Manual Storybook/screen-reader
checks remain deferred; the automated E2E and agent visual checks above
passed.

The historical review sections below retain their original run results.
This section supersedes earlier claims about strict entry validation,
entry editing, browsing reads, and evaluation authorization.

## Outcome

Content editors can create structured MDX entries from a collection
folder without writing frontmatter. Designers configure flat fields,
limits, optional slug behavior, and the Markdown template in the
Builder.

## High-level goals

- [x] Keep folders ordinary in the database and derive collection
behavior from a direct **collection.json**.
- [x] Add **Use as content collection** to folder creation.
- [x] Give designers a visual configurator for the JSON Schema and MDX
template.
- [x] Show a collection badge and a generic **New entry** action.
- [x] Generate independent MDX entries from the configured schema and
template.
- [x] Keep the current page unchanged after creating an entry; no
automatic preview navigation.
- [x] Select direct MDX entries with URLPattern filename rules; keep
ignored files as ordinary supporting assets.
- [x] Keep reserved collection files out of collection query results and
generated content indexes.
- [x] Preserve broken collections as visible, repairable collection
folders.
- [x] Restore normal folder behavior when **collection.json** is
removed.

## Implementation todos

- [x] Add the Content Engine collection contract, default
schema/template, slug normalization, validation, serialization, and
entry generation.
- [x] Discover collections from Assets files without a database folder
type or migration.
- [x] Create **template.mdx** and **collection.json** when the folder
checkbox is selected.
- [x] Add basic field controls for text, long text, slug, number,
integer, and Boolean values.
- [x] Support required fields, string length limits, numeric limits,
template defaults, and generated slugs.
- [x] Add designer collection settings and template editing.
- [x] Remove dynamic preview-page configuration and ignore its legacy
metadata.
- [x] Add the schema-generated **New entry** form and creation endpoint.
- [x] Preserve reserved-file portability in authenticated export/import
while excluding it from content query and generated-index results.
- [x] Document the feature in the authoritative Content Engine guide.

## Deep review checklist

A checked item means the area was inspected and evidence was recorded.
It does not mean the behavior passed. Unresolved defects and product
decisions remain listed in **Review findings**.

### 1. Product contract

- [x] Decide whether the collection checkbox creates defaults
immediately or opens the configurator first.
- [x] Decide whether existing folders can be converted through the UI.
- [x] Define collection activation when **collection.json** exists but
is invalid.
- [x] Define an entry: every valid direct MDX child or only files
created through **New entry**.
- [x] Confirm collection membership is direct-child only and nested
folders can define their own collections.
- [x] Define designer, content editor, viewer, service, CLI/MCP,
old-client, and direct-API authority.
- [x] Decide whether content mode is simplified UI or a server
authorization boundary.
- [x] Decide whether slugs are permanently immutable.
- [x] Decide whether schema changes may invalidate existing entries or
must migrate them.
- [x] Decide whether invalid unused collections block unrelated queries
or whole-project publication.
- [x] Decide what happens to the preserved template after collection
removal.
- [x] Define the promised JSON Schema dialect and supported subset.
- [x] Decide whether temporary invalid collection states are admitted
for repair.
- [x] Define duplicate-request semantics: no duplicates, exactly one
success, and retry behavior.
- [x] Decide whether entries require valid frontmatter only or valid
complete MDX.

### 2. State machine

- [x] Review ordinary folders and root-level **collection.json**.
- [x] Review empty-folder, template-only, config-uploading, and
config-loading initialization states.
- [x] Review ready collections.
- [x] Review invalid JSON, UTF-8, size, schema, and duplicate-config
states.
- [x] Review missing, duplicate, wrong-type, oversized,
invalid-frontmatter, and invalid-MDX template states.
- [x] Review forbidden direct files, invalid entries, and slug/filename
mismatches.
- [x] Distinguish transient read/network failures from persistent
invalid content.
- [x] Review removing, removed, moved, renamed, restored, duplicated,
and deleted collection states.
- [x] For every state, inspect visibility, allowed actions, server
admission, query/publish behavior, recovery, and concurrent-client
behavior; record recovery behavior.

### 3. Activation and deactivation paths

- [x] Review checkbox creation.
- [x] Review create/upload/rename/move/paste/duplicate of
**collection.json**.
- [x] Review restore, import, project duplication, and sync behavior.
- [x] Review delete/rename/move/replace of **collection.json**.
- [x] Verify all activation paths produce the same state and validation.
- [x] Verify all deactivation paths restore ordinary-folder behavior
without accidental data loss.

### 4. Folder membership and nested collections

- [x] Verify only direct children are collection entries or reserved
files.
- [x] Verify a nested folder with its own direct config becomes its own
collection.
- [x] Verify parent rules do not leak into nested folders.
- [x] Define filename and extension case sensitivity.
- [x] Define duplicate logical-name behavior over generated storage
names.
- [x] Define how uploading, deleted, and stale assets affect folder
state.
- [x] Review moving entries out and between collections with different
schemas.
- [x] Review ordinary subfolders with arbitrary files inside a
collection.
- [x] Review template behavior after collection removal.

### 5. Roles and authorization

- [x] Review config/template listing and download for every role.
- [x] Review config/template content edits for every role.
- [x] Review config/template rename, move, restore, and deletion for
every role.
- [x] Review entry create/edit/rename/move/delete/restore for every
role.
- [x] Review generic upload and subfolder operations for every role.
- [x] Review collection removal and repair for every role.
- [x] Verify server enforcement separately from hidden or disabled
controls.
- [x] Recheck permission changes while operations are in flight.

### 6. Mutation surfaces

- [x] Review REST upload-ticket creation and upload completion.
- [x] Review asset-content replacement and asset metadata changes.
- [x] Review runtime add/update/duplicate operations.
- [x] Review sync patches and direct persistence helpers.
- [x] Review clipboard, drag/drop, move, rename, and duplicate paths.
- [x] Review import/export, restore, and project/folder duplication
paths.
- [x] Review delete-unused-assets and recursive folder deletion.
- [x] Verify invariants at the final persistence boundary, not only in
UI preflight.

### 7. Concurrent operations

- [x] Review create entry against schema save, template save, raw config
edit, and collection removal.
- [x] Review edit/delete/restore entry against schema and template
changes.
- [x] Review two collection-settings editors and raw editor versus
configurator.
- [x] Review schema writer A interleaved with template writer B.
- [x] Review upload/move/rename against collection activation and
removal.
- [x] Review template/config deletion against creation and publication.
- [x] Review folder deletion/move and project switch against all async
operations.
- [x] Review final validity, conflict detection, cleanup, user outcome,
cache invalidation, and publish behavior across the implemented
intersections; record conflict and recovery behavior.
- [x] Review retry after the server commits but the HTTP response is
lost.

### 8. Collection initialization and recovery

- [x] Review project switch, permission revocation, dialog close, folder
rename/move/delete, and conflicting uploads during initialization.
- [x] Verify retry is idempotent and does not duplicate reserved files.
- [x] Verify partial setup has a visible finish, retry, or cleanup path.
- [x] Verify generic actions cannot corrupt the initialization window.
- [x] Verify project and folder identities are captured for the whole
operation.
- [x] Review asset-count, storage, filename, and folder limits with
cleanup.

### 9. JSON Schema conformance

- [x] Confirm the exact draft and validate configs against its
meta-schema.
- [x] Review unknown dialects, boolean schemas, malformed keyword types,
and duplicate JSON keys.
- [x] Review refs/defs, recursion, composition, conditionals,
dependencies, and nullable/multiple types.
- [x] Review enum, const, format, pattern, patternProperties, and
additionalProperties.
- [x] Review min/max properties, array constraints, contains, and
uniqueItems.
- [x] Review exclusive numeric bounds and all string/number/integer
limits.
- [x] Differential-test validation against an independent draft 2020-12
implementation.
- [x] Verify unsupported keywords are rejected or explicitly documented,
never silently ignored.

### 10. Schema resource limits and hostile input

- [x] Bound schema bytes, depth, field count, enum size, and reference
depth.
- [x] Bound validator compilation and validation time.
- [x] Review recursive references and excessive composition branches.
- [x] Review catastrophic patterns and regex denial of service.
- [x] Review huge labels, keys, defaults, and values.
- [x] Review prototype-sensitive and structurally awkward field names.
- [x] Review memory and CPU amplification across many collections.

### 11. Configurator ownership and round trips

- [x] Define owned, preserved, read-only, and rejected schema paths.
- [x] Verify parse/save/parse semantic equivalence and repeated-save
idempotence.
- [x] Verify a UI edit changes only explicitly owned schema paths.
- [x] Review property/form order and required-array order.
- [x] Review unknown root, property, and Webstudio extension metadata.
- [x] Review editable primitive fields that also contain enum, const,
format, composition, readOnly, or custom constraints.
- [x] Review type changes for incompatible preserved or removed
keywords.
- [x] Review rename/removal collisions with unsupported properties and
requirements.
- [x] Surface hidden preserved rules that can cause unexplained
validation errors.
- [x] Review field ordering/reordering.
- [x] Review choosing/changing slug field, slug source, and template
filename.
- [x] Review turning newly added fields into slug or slug-source fields.

### 12. Required/default/unset semantics

- [x] Review absent, empty string, false, zero, null, default, and
user-cleared values for every field type.
- [x] Define required-property versus non-empty-value behavior.
- [x] Verify required Boolean false remains valid and understandable.
- [x] Review optional Boolean absence versus explicit false.
- [x] Review explicitly clearing template-backed values.
- [x] Review JSON Schema default as annotation versus persisted entry
value.
- [x] Verify precedence among template values, schema defaults,
generated slug, and user values.
- [x] Review hidden/non-renderable required fields and template
requirements.
- [x] Verify a collection cannot be ready when every entry form must
fail.

### 13. Schema evolution and migration

- [x] Review add/remove optional and required fields.
- [x] Review field rename and label-only changes.
- [x] Review type changes and tightened/relaxed limits.
- [x] Review enum option, default, slug field/source, template name, and
additionalProperties changes.
- [x] Verify compatible save ordering and identify impossible two-write
migrations.
- [x] Decide whether entries are rewritten and whether migration is
atomic.
- [x] Review entry changes after validation but before schema commit.
- [x] Ensure designers see all incompatible entries or an actionable
summary.

### 14. Template, YAML, and MDX

- [x] Review absent/empty frontmatter and empty YAML.
- [x] Review BOM, CRLF, final-newline, and alternate YAML delimiter
behavior.
- [x] Review duplicate keys, anchors, aliases, tags, comments, quoting,
and multiline strings.
- [x] Review Unicode and byte limits.
- [x] Verify frontmatter and starter-body preservation.
- [x] Review invalid MDX bodies, imports, exports, expressions, and
unsafe nodes.
- [x] Review missing, renamed, duplicate, wrong-type, and oversized
templates.
- [x] Verify template changes affect future entries only.
- [x] Review reserved template exposure through direct asset URLs and
build copying.

### 15. New-entry form and creation

- [x] Verify every required field is renderable or supplied safely.
- [x] Verify deterministic field order and correct controls/constraints.
- [x] Compare client and authoritative server validation.
- [x] Prevent submitted values from overwriting protected hidden fields.
- [x] Preserve values and focus after failed creation.
- [x] Review per-field errors, error summary, and first-error focus.
- [x] Prevent duplicate submission and define lost-response recovery.
- [x] Verify local sync, reload persistence, and project-switch
handling.
- [x] Review no/stale preview page, query delay, and draft-filtered
entries.
- [x] Review large forms and narrow/mobile layouts.

### 16. Entry editing and lifecycle

- [x] Review body-only, frontmatter-only, and combined edits.
- [x] Review invalid intermediate frontmatter and invalid complete MDX.
- [x] Review slug change, filename rename, and reference consequences.
- [x] Review delete, restore, move out, move between collections,
duplicate, paste, and undo.
- [x] Review slug reuse after delete and restore collision.
- [x] Review edits concurrent with schema/template changes.
- [x] Review repair of legacy invalid entries and blocked intermediate
saves.
- [x] Decide whether existing entries receive a generated settings form
or raw MDX editing.

### 17. Slug and filename identity

- [x] Review slug source selection, same slug/source field, manual
override, and visible final normalization.
- [x] Enforce lowercase-dash identity independently of editable schema
metadata.
- [x] Review empty normalized slugs and template/schema slug defaults.
- [x] Review diacritics, non-Latin scripts, and emoji-only sources.
- [x] Use locale-independent case comparison and Unicode normalization.
- [x] Review character/byte limits and case-insensitive filesystems.
- [x] Review collisions with reserved config/template filenames.
- [x] Review concurrent creation, stale reservations, deletion, reuse,
restoration, and URL encoding.

### 18. Collection-root restrictions

- [x] Review upload, external drop, internal drag, generic text
creation, paste, duplicate, move, rename, and extension changes.
- [x] Review runtime add, sync patch, import, and restore bypasses.
- [x] Review upload tickets completed after activation.
- [x] Review moving or renaming **collection.json** through metadata
changes.
- [x] Review dedicated entry creation, initialization, repair, body
editing, and collection removal exceptions.
- [x] Verify subfolder creation and arbitrary files inside subfolders
remain allowed.

### 19. Storage, database, idempotency, and cleanup

- [x] Review object-without-File, File-without-Asset, stale UPLOADING,
and soft-deleted states.
- [x] Review storage success/database failure and database
success/storage failure.
- [x] Review postcheck failure, object cleanup, row cleanup, and cleanup
failure.
- [x] Verify cleanup preserves the original error and cannot delete
another request's asset.
- [x] Review timeout/lost response after commit and safe retries.
- [x] Review reservation lookup/cleanup failure and expiration.
- [x] Review failed creation cleanup and distinguish incomplete uploads
from existing entries with repairable field errors. Existing field
errors do not hide entries or block publication.
- [x] Verify orphan reclamation and project-scoped cleanup.

### 20. Query, build, and publication

- [x] Verify config/template exclusion only for an identified
collection.
- [x] Verify ordinary root-level **collection.json** remains ordinary
content.
- [x] Review template queryability after collection removal.
- [x] Validate actual entry frontmatter and decide complete-MDX
requirements.
- [x] Review invalid content versus transient read failure.
- [x] Decide invalid-collection blast radius across queries, field
catalog, build, and publish.
- [x] Review direct asset IDs, document references, build copying, and
public URLs for reserved-file leakage.
- [x] Verify preview and publish use the same collection rules.

### 21. Cache and snapshot consistency

- [x] Verify config, template, entries, filenames, folder movement,
activation, and removal affect source revision.
- [x] Verify immutable/revision-pinned reads under concurrent swaps.
- [x] Verify config and template come from one logical version.
- [x] Review revision consistency between entry reads and indexing.
Queries do not require entry-schema validation.
- [x] Review rejected-promise caching and retry after repair.
- [x] Verify no cross-project cache collision.

### 22. Performance and availability

- [x] Review sequential versus bounded parallel reads.
- [x] Review request cancellation and repeated preview-query work.
- [x] Review rate limits and resource-exhaustion protection.
- [x] Review progress/cancellation and actionable limit errors.

### 23. Invalid-state repair

- [x] Review repair for every config, template, forbidden-file, entry,
and slug mismatch state.
- [x] Show exact folder, file, field, and rule.
- [x] Distinguish retryable transport errors from content errors.
- [x] Provide missing-template creation despite normal root
restrictions.
- [x] Provide duplicate-file deletion and forbidden-file move recovery.
- [x] Preserve raw repair when the configurator cannot parse the schema.
- [x] Show collection health after repair.
- [x] Prevent custom-template leakage to content editors when config
cannot identify it.

### 24. Entry navigation

- [x] Remove dynamic preview configuration and automatic navigation
after entry creation.
- [x] Verify entry creation leaves the selected page unchanged, with or
without a slug field.

### 25. Accessibility and responsive behavior

- [x] Review native form submission and required semantics beyond an
asterisk.
- [x] Review linked errors, error summary, live announcements, and busy
state.
- [x] Review focus after errors, add/remove field, key edits,
confirmation transitions, and dialog close.
- [x] Review dirty-close warning, explicit cancel/retry, and durable
feedback beyond toasts.

### 26. Portability, compatibility, and rollback

- [x] Review export/import, folder/project duplication, and restore
ordering.
- [x] Verify logical filenames, not storage names, define the portable
contract.
- [x] Review case-insensitive filesystem behavior.
- [x] Review rollback after collection files already exist.
- [x] Verify removal or rollback leaves content recoverable.
- [x] Confirm database/migration assumptions.

### 27. Observability and supportability

- [x] Review state-transition and validation diagnostics.
- [x] Track cleanup failures and orphaned reservations.
- [x] Make publish errors actionable without logging private content.
- [x] Ensure client errors do not expose stack traces or sensitive
internals.

### 28. Verification strategy

- [x] Unit-test schema, slug, merge, serialization, and validators.
- [x] Demonstrate each new regression test fails without its fix.
- [x] Run affected unit suites, typechecks, lint, docs checks, E2E,
fixture-impact review, and final diff review.
- [x] Review authoritative documentation impact.

## Persistence and recovery

No database migration is needed. Folders remain ordinary database rows;
their direct **collection.json** activates collection behavior. This PR
adds no table, column, index, RPC, folder type, or backfill.

- Setup writes the template first and **collection.json** last.
Interrupted setup leaves an ordinary folder and can be retried.
- Existing immutable File revisions, conditional Asset updates, and
upload reservations provide conflict detection and retry-safe entry
creation.
- Settings saves validate the intended template and schema together,
then write only the files that changed. Existing entry field errors stay
repairable and do not block unrelated saves, queries, or publication.
New entry creation remains strict.
- Invalid or interrupted collections remain visible and repairable.
Configuration/template structure errors remain distinct from entry-field
errors; entry-field errors do not invalidate the entire collection.
- Public/service publication excludes configuration and template files.
Authenticated CLI exports retain them for lossless import.

## Review findings

The implementation review is complete. Confirmed defects were fixed and
the following tradeoffs remain explicit.

### Fixed

- Enforced collection structure and permissions across REST, runtime
patches, import, query, and publication, while keeping existing
entry-field errors non-blocking for queries and publication.
- Preserved Undo/Redo, including restoring shared File revisions in
cloned projects.
- Rejected forged shared-file metadata while preserving metadata edits
on legacy files with null uploader provenance.
- Added retry and recovery behavior for setup, settings saves, lost
responses, and transient reads.
- Moved collection setup into one retry-safe server operation and kept
the local Builder update as one Undoable transaction.
- Centralized collection inspection and limited schemas to the flat
fields the configurator can represent. Opening a collection still reads
its entries in the background to report field errors.
- Guarded stale dialogs and deferred saves against permission changes,
protected files, project switches, and replaced revisions.
- Kept Asset settings open while choosing nested folders; the existing
browser regression fails before the fix and passes afterward.
- Preserved required Boolean false values, field-level errors,
dirty-close prompts, and inline Markdown editing.
- Separated authenticated portable exports from public runtime assets.
- Updated the authoritative Content Engine guide.

### Known tradeoffs

- A failure after template content saves but before configuration saves
can temporarily leave an invalid collection. Retrying the settings save
completes it; otherwise the visible files allow repair. The
template/config rename endpoint remains retry-safe and rolls back a
rename when its config write fails.
- Cloned-project Undo can retain an unused prior File revision. Separate
read-then-delete cleanup could race with another restore, so this path
retains the revision.
- Build/asset persistence and import already use multiple calls. This PR
validates before writes and provides retry/conflict handling; it does
not provide a transaction spanning every resource or a global collection
lock.
- Opening a collection loads its configuration and template through
shared Builder discovery, then checks entry frontmatter in the
background for field errors. Unchanged configuration/template content is
reused across controls and entry edits. Frontmatter checks still require
entry reads; ordinary folder browsing does not discover every
collection’s file content.

## Latest unstaged-change review — 2026-09-07

- [x] Review collection settings, validation, autosave/error recovery,
entry creation, reserved-file actions, and conversion flows.
- [x] Review shared labels, list keyboard handling, dialog focus/Escape,
buttons, and panel spacing migrations.
- [x] Fix Enter/Space list activation lost when arrow navigation
replaced the existing press handler.
- [x] Recognize historical generated ASCII/Unicode slug patterns, use
the bundled Unicode slug schema, and save a locally resolved $ref
without changing existing filenames.
- [x] Prevent duplicate slugless entries after a lost response: New
entry sends a stable UUID per attempt, reused by the repository's
existing filename/content retry checks. No database migration. Callers
omitting the optional requestId retain the previous behavior.
- [x] Preserve Content Block template validation independently of page
HTML placement, without accepting invalid rendered nesting.
- [x] Demonstrate the new review regressions fail without their fixes
and pass afterward.
- [x] Review authoritative documentation and update legacy slug
behavior.
- [x] Run fixtures and inspect generated changes: none.
- Deferred manual verification: Storybook screenshots were not
authorized for this pass.
- Historical evaluation status: not run in this pass; superseded by the
final verification section above.

## Verification

Local checks for commit **2b8195692e**:

- Builder app: 1,739 tests; Builder core: 1,150 tests. After the final
fixes, entry/settings tests and shared asset REST checks also passed,
including the added stable-retry test.
- Content Engine: 535 tests.
- Asset Uploader: 279 tests.
- Design system: 249 tests.
- Project Build: 2,392 tests.
- All five affected package typechecks, changed-file lint/formatting,
package boundaries, and Content Engine documentation tests passed.
- Complete fixture link/sync/build pipeline passed with no tracked
fixture changes.
- Staged diff and credential-pattern checks passed. No database schema,
migration, or generated database type changes. The lockfile includes the
collection validator and filename matcher dependencies.
- CI for this commit is pending. Builder E2E, manual
screen-reader/mobile checks, Storybook screenshots, and agent
evaluations were not run in this pass.

## MDX editing review — commit c3702a7238

- [x] Review all 36 local changed files: MDX parsing/materialization,
template enforcement, mutation commits, persistence, Assets refresh,
Navigator, source binding, editor diagnostics, dialogs, and query menus.
- [x] Validate new custom components at the shared Builder mutation
commit before applying tree changes. Cover move, reparent, paste, nested
paste, duplicate, and batched operations; preserve unchanged authored
content.
- [x] Match template names and component structure, including renamed
templates, instead of accepting any component type found in Templates.
- [x] Remove the registered-component fallback from MDX materialization
and completion; report untemplated JSX in the editor.
- [x] Preserve unresolved JSX until its placeholder is deleted, then
persist its removal without clearing the Assets resource during refresh.
- [x] Keep custom designer content outside the MDX body; omit the extra
body for fresh unconnected Content Blocks.
- [x] Resolve Navigator's MDX-file action from the active content root
for dynamic sources.
- [x] Fix the first dialog drag jump while honoring subsequent explicit
size changes; contain long query menus and prevent editor Backspace from
deleting instances.
- [x] Demonstrate new review regression failures before the fixes and
passing results afterward.
- [x] Review and update authoritative Content Block documentation.
- [x] Run the complete fixture pipeline; no generated fixture changes.
- Historical evaluation status: deferred in this pass; superseded by the
final verification section above.
- Historical verification limits: no Storybook screenshots, Builder E2E,
or manual accessibility checks in this pass. See the final verification
section for the current E2E result; manual checks remain deferred.

### Latest verification

- Builder affected suites: **624 tests passed**.
- Project Build full suite: **2,394 tests passed**.
- Design system full suite: **250 tests passed**.
- Query Builder React full suite: **14 tests passed**.
- Component Registry full suite: **7 tests passed**.
- All five affected package typechecks, repository lint, changed-source
formatting, staged diff checks, and credential-pattern scan passed.
- Fixture link/sync/build passed without generated changes. No database
migration.
- CI for this commit has not yet been verified.

The new mutation-commit check covers Builder operations affecting loaded
external content roots. It is not a claim that every remote API mutation
is now guarded by this client-side check.

Closes #6316


## Local-change review — 2026-09-08

- [x] Review all 47 changed files and trace collection setup, filename
filters, public metadata/upload APIs, sync patches, Content-mode write
targets, local folder persistence, and MCP downloads.
- [x] Fix ignored MDX files being renamed into entries through the
metadata API. Selected filenames require New entry.
- [x] Fix supporting-asset moves/renames being blocked by unrelated
invalid entries. Configuration/template checks still run. The later
entry-repair work also removed entry-field validation as a blocker for
unrelated writes and publication.
- [x] Validate frontmatter directly with Ajv, resolve the bundled slug
$ref without network fetching, preserve the flat schema contract, and
avoid retaining old validator instances.
- [x] Verify Unicode generation, legacy-pattern upgrades, sibling
constraints, field error paths, optional binding paths, referenced-file
permissions, and independent queued edits.
- [x] Review authoritative documentation, correct contradictory
referenced-write guidance, and regenerate CLI/MCP documentation.
- [x] Demonstrate new review regression failures with the fixes
disabled, then restore and pass them.
- [x] Pass Content Engine (553), asset uploader (284), SDK (570),
project-build (2,394), MCP command (61), and collection/Content-mode
browser tests (279), plus expression tests and documentation checks.
- [x] Pass seven affected package typechecks, repository lint,
formatting, package boundaries, and fixture link/sync/build. No
generated fixture changes or database migration.
- Historical evaluation status: deferred in this pass; the user
subsequently authorized the full suite. See the final verification
section above.
- Historical verification limits: no Builder E2E or manual
visual/accessibility checks in this pass. See the final verification
section for the current E2E result; manual checks remain deferred.
2026-09-09 21:31:04 +02:00
Oleg Isonen 722b3af113 feat: Copy links to instances from the context menu (#6342)
Right-click an instance in the Navigator and choose **Copy link to
instance** to share its full Builder URL. Opening it restores the page
and the same instance occurrence, including descendants of shared Slots.
The shared menu also exposes the action on the canvas.

Copying and opening a link use the same existing
`getDeepLinkedInstanceSelection` function, moved unchanged from
`use-switch-page.ts` into `shared/instance-utils/link.ts`, alongside URL
creation and the existing instance URL parser. It validates the instance
path and page access. The context menu and pre-publish “Show element”
links both call `getInstanceLink(instanceSelector)`. It lives in
`shared/instance-utils/link.ts` and owns validation and the complete URL
setup: project, page, full selector, origin, share token, editor mode,
and safe mode, using the existing `builderUrl`. There is no separate
link-permission implementation.

The link keeps the current share access and editor mode, clears
unrelated scroll anchors, and shows clipboard success or failure. Body
is supported; Global Root and multiple selections are unavailable.

Instance-only navigation now preserves loaded Builder data: the route
comparison recognizes the `instance` parameter as well as the older
`instanceId`. A functional regression test confirms that changing the
target does not change the data URL comparison, while changing share
access does.

MCP guidance explains how to extract the target ID from the link for
`inspect-instance`, verify the project and shared Slot context, and
avoid guessing when an instance no longer exists. This does not add a
URL-taking MCP endpoint.

1. [x] Add the menu action and reuse complete instance-link construction
with pre-publish links.
2. [x] Share existing instance-link validation between copying and
opening links.
3. [x] Update the Navigator and MCP guidance; regenerate CLI
documentation.
4. [x] Extend the existing functional test to round-trip a shared Slot
URL, including share access and editor mode. Verify it fails without the
instance parameter and passes after restoration.
5. [x] Remove the React/clipboard test harness and duplicate
permission-test matrix. Confirm no fixture inputs or outputs are
affected.

Validation: 28 focused Builder tests, Builder typecheck, repository
lint, and `git diff --check` pass on this revision. The earlier CLI
documentation checks passed and their inputs are unchanged. An earlier
GPT-5.4 mini subagent at low reasoning passed a focused native MCP test
against commit 072a8dc534: given only a link and text-edit request, it
changed the correct heading among three identical-looking headings,
leaving all other fixture records unchanged. It used four MCP calls with
broad instance/text reads. That agent test used an isolated local
fixture; it was not a production or shared-Slot agent test. Screenshots
and the full agent evaluation suite were not run.

Closes #4610
Ref #4364
2026-09-08 15:23:29 +02:00
Oleg Isonen 05706b5425 feat: Make Content Block templates the MDX authoring interface (#6307)
Closes #6306

## Outcome

Content Block templates are now the public component model for MDX
authoring.

- Standard Markdown resolves through matching Content Block templates.
- PascalCase JSX resolves to a named template first, then to a
registered component.
- Adding or changing a template immediately affects matching MDX,
including content authored before that template existed.
- Every supported standard MDX element is present in new Content Blocks
by default.
- Template `name` is a stable PascalCase JSX identifier. `label` remains
optional display text only.
- Lowercase JSX represents native HTML and SVG without exposing
`ws.element` in authored syntax.
- Unknown, ambiguous, and unsafe JSX stays in source, produces a
source-ranged Builder diagnostic, and is omitted from published output.
- Frontmatter images preserve exact `$ref` values and use the Asset
description for `alt` text.
- Keyboard and mouse insertion from the slash menu remove the trigger
slash and preserve the selected template.

## Public MDX contract

- Markdown is canonical for standard prose.
- Lowercase JSX creates native elements:
`<section><p>Text</p></section>`.
- PascalCase JSX uses templates and registered components:
`<PromotionCard />`, `<Heading tag="h1">Title</Heading>`, and `<YouTube
src="https://youtu.be/KI5JHpzBK1s" />`.
- Component-name collisions use stable qualified identifiers such as
`Checkbox` and `RadixCheckbox`.
- A self-closing template keeps its designed default subtree. An
explicitly paired empty template clears it.
- Static quoted values and bare booleans are supported.
- Imports, exports, expressions, spreads, functions, executable
JavaScript, unsafe attributes, `<$.*>`, and authored `<ws.element>` are
rejected.
- `ws` remains only for compiler primitives with no React component.

## Implementation

- [x] Add stable, unique template names and keep them separate from
labels.
- [x] Parse and validate Markdown and JSX through their ASTs with
precise source ranges.
- [x] Share component identity between template JSX, Content MDX,
Builder materialization, reload, copy, and publication.
- [x] Resolve templates live after add, edit, rename, reorder, removal,
undo/redo, collaboration updates, and reload.
- [x] Preserve authored props, children, Asset references, template
defaults, and the exact slash-menu template choice.
- [x] Make one typed MDX semantic inventory drive default templates and
slash-menu eligibility.
- [x] Move component-dependent template assembly into the component
registry.
- [x] Replace production `$`, `animation`, and `radix` JSX namespaces
with direct component imports.
- [x] Keep only explicit compiler primitives in `ws` template JSX.
- [x] Migrate repository templates, fixtures, stories, examples, MCP
guidance, CLI guidance, and generated outputs.
- [x] Add Builder unit and Playwright coverage for authoring,
diagnostics, insertion, synchronization, reload, and publication.
- [x] Review and update the authoritative Content Block and generated
MCP documentation.

## Compatibility

- Persisted project data continues to use namespaced component IDs and
`ws:element` for generic HTML elements. No project-data migration is
required.
- Legacy `<ws.element ws:tag="…">`, `<ws.element ws:name="…">`, `<$.*>`,
`<animation.*>`, and `<radix.*>` input remains readable during the
compatibility window.
- Current serializers and repository-owned sources emit only canonical
Markdown, lowercase intrinsic JSX, or PascalCase identifiers.
- Untouched user MDX is not rewritten automatically. Legacy syntax is
canonicalized when its owning source is edited.
- Deferred follow-up: remove legacy namespace readers and proxy support
only after the canonical writer is deployed and existing projects are
backfilled. This is intentionally outside this PR.

## Verification

- [x] CI package tests, typechecks, lint, package boundaries, generated
assets, migrations, and size checks pass.
- [x] All six Builder E2E shards pass.
- [x] Fixture checks pass on Ubuntu and Windows.
- [x] Targeted local Content Engine, Template, SDK, registry, Project
Build, migration, Builder, component, CLI, and Playwright tests pass.
- [x] Runtime contracts, CLI docs, and MCP docs were regenerated from
their owning sources.
- [x] Local fixture generation completed without an unexpected fixture
diff.
- [x] Documentation impact reviewed and included in this PR.
- [x] Review and approve the intentional List visual snapshot change.
- [x] Run the complete agent evaluation suite after explicit approval.
- [x] Run the network fixture link/sync pipeline against the matching PR
deployment after explicit permission.

## Known visual change

The generated List story now targets its actual default `ul` element
instead of `ol`, so the default list styling appears in the visual
snapshot. The visual change was reviewed and the approved visual check
passes.
2026-09-06 15:19:54 +02:00
Oleg Isonen 2e39477303 feat: Expose Force Mount for Collapsible Content (#6338)
## Problem

Closed Collapsible Content is removed from the HTML, and the Builder
does not expose Radix’s Force Mount option.

## Solution

- Expose **Force Mount** as a boolean on Collapsible Content.
- Enable it for newly inserted Collapsible templates.
- Keep closed force-mounted content visually collapsed with template
styles.
- Preserve the existing Radix default for existing instances that do not
set the prop.
- Add regression coverage for the template default and generated Builder
metadata.

## Checklist

- [x] Expose the Force Mount control
- [x] Keep new Collapsible content mounted while closed
- [x] Preserve existing-instance behavior
- [x] Regenerate Collapsible prop metadata from source
- [x] Add regression coverage
- [x] Review documentation impact and update the Collapsible guide
- [x] Verify fixture inputs and outputs are not affected

## Verification

- [x] `pnpm --filter @webstudio-is/sdk-components-react-radix test` — 20
tests passed
- [x] `pnpm --filter @webstudio-is/sdk-components-react-radix typecheck`
- [x] `pnpm --filter @webstudio-is/sdk-components-react-radix build`
- [x] Targeted Oxlint and formatting checks
- [x] `git diff --check`

## Known limitations

Storybook visual verification was not run because it requires separate
approval.

Closes #6337
2026-09-05 17:36:25 +02:00
Oleg Isonen 55920c57c4 fix: Show all Markdown and Assets query diagnostics (#6308)
Ref #6305

## Outcome

Show actionable Markdown, MDX, and Assets query problems in the editor,
query Diagnostics, API, and MCP.

The current implementation substantially improves source diagnostics,
query error aggregation, query scoping, exact paths, MCP feedback, and
loading states. The exhaustive audit below also records what is not
complete yet.

## Completion rule

An error state is complete only when all applicable checks pass:

- [x] The editor uses the production validator and shows every issue for
the open file.
- [x] Query execution uses the same validation and returns every issue
for every file that matches the query, not only the returned page.
- [ ] MCP can reproduce the state through a real tool call and returns
the same code, severity, message, file, and location.
- [x] Diagnostics shows fatal errors and nonfatal warnings in the
correct place.
- [x] Exact file is always present; line and column are present whenever
the parser can determine them.
- [x] Multiple issues in the same file, multiple files, and multiple
validation phases are returned together.
- [ ] A regression test proves failure without the behavior and success
with it.

## Cross-surface contract

### Required validator parity

- [x] Editor, query execution, and MCP file operations invoke the same
exported production file-validation pipeline for byte decoding,
source/frontmatter, Markdown/MDX, Content Block semantics, and
references. Copied schemas, adapter-only checks, or reimplementations do
not count as parity.
- [x] Query builder, query execution, `validate-asset-query`, and
`preview-asset-query` invoke the same exported structural and
catalog-aware semantic query validator with the same project snapshot
and context.
- [x] Every surface preserves the same complete, deterministically
ordered diagnostic array: severity, code, message, phase/scope, asset
ID, file path, line/column/range, query path, and
fatal-versus-warning/result behavior. UI, API, and MCP serializers must
not drop or remap fields.
- [ ] A table-driven contract suite calls the real editor, query, API,
and MCP entry points—including MCP upload, update, download, validate,
and preview—and compares full normalized arrays for multiple errors,
multiple warnings, multiple files, and pre-pagination matches.

- [x] Markdown and MDX source syntax/frontmatter use
validateTextAssetSource in editor, query compilation, and MCP file
tools.
- [x] Query request execution and MCP local parsing use
assetQueryRequest.
- [x] Fatal query setup errors retain all detected semantic errors and
accompanying warnings.
- [x] Nonfatal query warnings retain a stable code and exact query path.
- [x] Source diagnostics retain severity, phase, code, asset ID, path,
and available source position.
- [x] Query Diagnostics excludes diagnostics produced only by unrelated
published queries.
- [x] Query, Preview, and Diagnostics tabs show loading states.
- [x] Authenticated diagnostics can show a sanitized internal message;
public/pre-auth errors remain redacted.
- [x] Share byte-level UTF-8 validation instead of decoding separately
in query and MCP.
- [x] Make the query editor and MCP call one literal shared
query-validation function before execution.
- [x] Share cross-file reference validation across editor, query, and
MCP file operations.
- [x] Share contextual Content Block/template validation across all
applicable surfaces.
- [x] Preserve an invalid stored query in the editor and explain it
instead of replacing it with defaults.
- [x] Define and enforce whether “matches the query” includes every
pre-pagination match; current source validation follows
returned/paginated IDs.
- [x] Continue through source, reference, and runtime phases where safe
so one phase does not hide later issues.
- [x] Map every user-triggerable graph/storage/runtime failure to a
structured public error instead of INTERNAL_ERROR.
- [ ] Label candidate-file warnings separately from returned-file
diagnostics.

## File transport and encoding

- [x] Empty Markdown and MDX are accepted.
- [x] UTF-8 BOM and CRLF are accepted.
- [x] Whole Markdown/MDX file over 1 MiB is detected.
- [x] Missing or unavailable selected content is reported.
- [x] Invalid UTF-8 frontmatter returns the same structured diagnostic
in query and MCP.
- [x] Invalid UTF-8 body returns the same structured diagnostic in query
and MCP.
- [x] MCP download must use fatal UTF-8 decoding rather than replacement
characters.
- [x] Validate bytes before committing an upload, or return the
committed Asset ID and explicit cleanup state when post-commit feedback
fails.
- [x] Reject filename/format mismatches instead of allowing either hint
to bypass Markdown/MDX validation.
- [x] Storage status, missing body, invalid metadata, revision mismatch,
format mismatch, short read, and excess read each return structured file
context.
- [x] Content changing during validation has a clear retryable conflict
diagnostic.

## Frontmatter

- [x] No frontmatter.
- [x] Empty frontmatter.
- [x] Opening delimiter without a closing delimiter.
- [x] Frontmatter over 64 KiB.
- [x] Invalid YAML grammar.
- [x] Multiple YAML parser errors.
- [x] Duplicate keys.
- [x] YAML aliases.
- [x] Unsupported YAML tags.
- [x] Scalar or array root instead of an object.
- [x] Non-JSON values, NaN, Infinity, and unsafe integers.
- [x] Nesting deeper than 8.
- [x] More than 256 fields/array entries.
- [x] One string over 16 KiB.
- [x] Normalized indexed properties over 64 KiB.
- [x] Frontmatter warnings remain nonfatal and visible.
- [x] Collect all post-parse normalization/limit errors; this phase
currently stops at the first.
- [ ] Prove every state above through real MCP upload, update, download,
query, and Diagnostics paths.

## Markdown body

- [x] Treat ordinary CommonMark/GFM as permissive; malformed-looking
Markdown and raw HTML are not automatically syntax errors.
- [x] Preserve and validate the body when frontmatter is invalid.
- [x] Detect the 1 MiB body/file limit.
- [x] Handle body extraction and bounded excerpts.
- [x] Return a shared structured invalid-UTF-8 body error from every
byte-reading surface.
- [ ] Distinguish author errors from internal
excerpt-limit/configuration errors.
- [ ] Prove body-size and decoding states through real MCP and
Diagnostics.

## MDX grammar and safety

- [x] Malformed MDX/JSX grammar.
- [x] Imports.
- [x] Exports.
- [x] Executable flow expressions.
- [x] Executable text expressions.
- [x] JSX expression attributes.
- [x] JSX spread attributes.
- [x] Arbitrary components.
- [x] HTML-looking JSX outside ws.element.
- [x] Unsupported/raw MDX node types.
- [x] Duplicate attributes and normalized alias collisions.
- [x] Event-handler attributes.
- [x] srcdoc, srcset, and dangerouslySetInnerHTML.
- [x] Unsafe URL protocols in link/source/action-like props.
- [x] Forbidden tags: base, link, meta, noscript, script, style,
template, title.
- [x] Conflicting, empty, or non-static ws:name and ws:tag.
- [x] More than 20,000 nodes.
- [x] Nesting deeper than 100.
- [x] More than 4,000 props.
- [x] Multiple recoverable unsafe-mdx warnings are collected.
- [ ] Collect multiple fatal grammar/structural errors when parser
recovery can do so; unrecoverable parsing currently stops at the first
fatal error.
- [x] Preserve sibling expression warnings when unsupported JSX is also
present; {first()}, <Card />, {second()} currently reports only the Card
warning.
- [ ] Prove every state above through real MCP and Diagnostics, not only
validator unit tests.

## Content Block semantics

- [x] Unresolved template warning exists.
- [x] Unknown template prop warning exists.
- [x] Incompatible template prop warning exists.
- [x] Design-only template prop warning exists.
- [x] Stale template prop warning exists.
- [x] Invalid materialized HTML content model error exists.
- [x] Run these checks for an MDX file even when no active editor
Content Block root has materialized it.
- [x] Run applicable semantic checks in query Diagnostics.
- [x] Run applicable semantic checks for MCP upload as well as
contextual update/download.
- [x] Return all Content Block frontmatter diagnostics; one path still
throws only diagnostics[0].
- [ ] Add real MCP and UI coverage for every semantic state.

## Cross-file references

- [x] Non-string $ref marker.
- [x] Invalid reference URL.
- [x] Invalid fragment encoding.
- [x] Invalid fragment.
- [x] Invalid JSON Pointer.
- [x] Missing referenced document.
- [x] Missing referenced JSON path.
- [x] Missing referenced local Asset warning.
- [x] Unsupported target representation.
- [x] Reference cycle.
- [x] Duplicate document ID or URL.
- [x] Missing graph source or target.
- [x] Root not found.
- [x] Duplicate/unused/unresolved reference.
- [x] Invalid resolved value.
- [x] Content identity conflict.
- [x] Target revision mismatch.
- [x] Representation selector required/invalid.
- [ ] Diagnose {$ref: value, extraKey: value} as an invalid marker or
explicitly document it as ordinary data.
- [x] Collect all bad references and targets instead of stopping at the
first graph failure.
- [x] Validate references from the editor and MCP file tools, not only
query compilation.
- [x] Map graph compilation/resolution, Markdown-document, and
JSON-document errors to exact structured Diagnostics rows.
- [x] Prevent an unrelated file's graph error or duplicate canonical URL
from blocking an exact-file query.
- [ ] Prove missing targets, cycles, bad representations, and bad JSON
paths through real MCP.

## Query request and structure

- [x] Invalid JSON, invalid request UTF-8, empty/non-object request, and
request over 512 KiB.
- [x] Missing query and unknown request/query keys.
- [x] Invalid result mode.
- [x] Malformed where/all/any/filter shape.
- [x] More than 32 filters or children.
- [x] Filter nesting deeper than 8.
- [x] Empty field path or segment.
- [x] Field path deeper than 9.
- [x] Bare properties path.
- [x] Unknown or nested standard field.
- [x] Missing or unknown operator.
- [x] Non-JSON operator value.
- [x] Invalid in value or more than 1,000 in values.
- [x] Non-boolean exists/isEmpty value.
- [x] More than 8 sort entries.
- [x] Invalid sort field or direction.
- [x] first/last without a sort.
- [x] Invalid output mode or includeMetadata.
- [x] Duplicate output fields or more than 256 fields.
- [x] No selected output.
- [x] Negative, fractional, or over-1,000 limit.
- [x] Negative, fractional, or over-1,000 offset.
- [x] Invalid content mode.
- [x] Invalid full/markdown-body-ref maxBytes outside 1..1 MiB.
- [x] Invalid range offset/length or range over 256 KiB.
- [x] Empty or over-255-character index revision.
- [x] Multiple simultaneous schema issues retain exact query paths.
- [x] Make content option objects strict; unknown keys are currently
stripped.
- [x] Show an invalid persisted resource body rather than silently
loading the default query.
- [x] Aggregate unknown-key errors with every other structural issue;
MCP currently stops at the first unsupported key.
- [x] Add field-value type validation, for example reject size gt
"large" and id contains {}.
- [x] Warn about unobserved dynamic fields selected only as output.
- [ ] Prove the full structural matrix through the MCP server/API end to
end; the current broad matrix tests the local adapter.

## Query semantic states

- [x] INCOMPATIBLE_OPERATOR is fatal for standard fields.
- [x] UNOBSERVED_FIELD is a nonfatal warning.
- [x] INCOMPATIBLE_OBSERVED_TYPES is a nonfatal warning.
- [x] STRUCTURED_SORT_ORDER is a nonfatal warning.
- [x] Every incompatible standard-field operator is collected.
- [x] Fatal semantic errors retain nonfatal warnings.
- [x] Successful warnings appear with exact query paths.
- [ ] Reproduce every catalog-dependent semantic state through real MCP.
- [x] Test repeated warnings at distinct query paths without accidental
deduplication.

## Query execution and hydration

- [x] Stale index revision.
- [x] result one matching multiple files, including matchedCount.
- [x] Candidate document limit of 1,000.
- [x] Result payload limit of 16 MiB.
- [x] Hydrated file count limit of 20.
- [x] Per-file hydration limit of 1 MiB.
- [x] Total hydration limit of 2 MiB.
- [x] Range limit of 256 KiB.
- [x] Binary content requested as text.
- [x] Invalid UTF-8 during hydration.
- [x] Missing, stale, or conflicting content identity.
- [x] Canonical metadata/byte-count mismatch.
- [x] Storage returning more bytes than requested.
- [x] Missing content reader.
- [x] Selected content not returned.
- [x] Runtime Asset URL unavailable.
- [x] Document graph document-count/per-file/total limits.
- [x] Classify candidate-document and result-byte limits as
CONTENT_LIMIT_EXCEEDED instead of INVALID_REQUEST.
- [x] Give unavailable URL, missing reader/content, storage, selection,
and assembly failures stable codes and exact asset/file details instead
of INTERNAL_ERROR.
- [x] Never silently turn an exact-file unavailable-content failure into
an empty successful result.
- [x] Add direct regression tests for duplicate identity, excess bytes,
and canonical byte mismatch.
- [ ] Reproduce every runtime/hydration state through real MCP.

## Scoping and aggregation

- [x] Errors and warnings from multiple loaded result files are returned
together.
- [x] Nonfatal errors do not suppress successful query results.
- [x] Unrelated published-query diagnostics do not replace current-query
diagnostics.
- [x] File path and available line/column are shown.
- [x] Exact nested query paths are shown.
- [x] Validate and report every pre-pagination matching file if that is
the product definition.
- [ ] Explain candidate metadata warnings when invalid metadata prevents
deciding whether a file matches.
- [ ] Stop including METADATA_PREPARATION_FAILED globally unless the UI
labels its candidate scope.
- [x] Aggregate reference errors instead of stopping at the first.
- [x] Aggregate across validation phases where safe.
- [x] Add a matrix for same-file multiple errors, multiple files,
outside-page errors, filter-only candidate files, and unrelated queries.

## Transport, authorization, cancellation, and internal failures

- [x] UNAUTHORIZED and FORBIDDEN.
- [x] REQUEST_CANCELLED in query HTTP paths.
- [x] Sanitized authenticated internal message.
- [x] Generic unauthenticated/public internal message.
- [x] Preserve structured causes for document-load HTTP
status/body/metadata failures.
- [x] Preserve structured causes for object-storage read failures.
- [x] Preserve structured causes for graph selection/assembly failures.
- [ ] Test MCP/API cancellation directly.
- [ ] Reproduce authorization, cancellation, storage, and
sanitized-internal states through real MCP where safe.

## MCP reproduction

- [x] Real MCP reproduction: multiple Markdown YAML errors.
- [x] Real MCP reproduction: multiple simultaneous structural query
errors.
- [x] MCP update returns Markdown and MDX source diagnostics.
- [x] MCP upload returns Markdown and MDX source diagnostics.
- [x] MCP download returns Markdown and MDX source diagnostics.
- [x] Temporary remote fixtures are removed after reproduction.
- [ ] Real MCP reproduction for every frontmatter code.
- [ ] Real MCP reproduction for every MDX fatal/warning class.
- [ ] Real MCP reproduction for every Content Block semantic diagnostic.
- [ ] Real MCP reproduction for every reference/graph error.
- [ ] Real MCP reproduction for every semantic query warning/error.
- [ ] Real MCP reproduction for every hydration/runtime/limit error.
- [ ] Real MCP reproduction for auth, cancellation, storage, and
internal errors where safe.
- [ ] A durable table-driven test proves code/severity/message/path
parity between editor, query, Diagnostics, and MCP.
- [x] Preserve stable top-level error identity and structured details
through MCP instead of flattening known errors to MCP_TOOL_FAILED.
- [x] Prove failed post-commit MCP feedback reports whether a mutation
committed and returns enough identity for cleanup.

## Real MCP recheck and follow-up fixes (2026-09-03)

Ran the branch CLI against the PR preview backend. The first run exposed
that the saved CLI origin pointed to a different backend, so those
results were discarded. Every MCP result below came from the PR preview.
All temporary remote and local fixtures were removed afterward. The
failures found in that run were then fixed and covered by regression
tests.

- [x] Exact broken Markdown query returns FRONTMATTER_INVALID with
severity, filename, message, line, and column.
- [x] Exact MDX query returns unsafe-mdx with severity, filename,
message, line, and column.
- [x] A no-extension query returns warnings from both matching Markdown
and MDX files.
- [x] A Markdown-only query excludes the unrelated MDX warning.
- [x] Two fatal MDX files inside the returned page are both reported;
matching warnings remain attached.
- [x] Structural query validation returns three independent errors with
exact paths in one call.
- [x] Semantic query validation returns three fatal operator errors and
two warnings with exact paths in one call.
- [x] MCP upload, update, download, and exact-file query return multiple
source diagnostics for the same MDX source.
- [x] Query preview reports a missing Asset reference as ASSET_NOT_FOUND
with the source file and reference.
- [x] All temporary MCP fixtures were deleted and the saved CLI
connection was restored.
- [x] Fixed after recheck: validate every matching file before
pagination, including broken files outside the returned page.
- [x] Fixed after recheck: unrelated graph failures no longer block an
exact-file query.
- [x] Fixed after recheck: update, download, editor, and query paths use
shared reference validation.
- [x] Fixed after recheck: graph failures map to structured diagnostics
with the exact file.
- [x] Fixed after recheck: shared fatal UTF-8 validation runs before
upload writes and preserves structured error identity.
- [x] Fixed after recheck: MCP rejects filename/format mismatches.
- [x] Fixed after recheck: MDX validation keeps sibling and nested
warnings around unsupported components.
- [x] Fixed after recheck: MCP preserves stable top-level error codes
and structured details.
- [x] Fixed after recheck: query and MCP serializers preserve full
normalized diagnostic fields.
- [x] Fixed after recheck: unknown keys are aggregated with all other
structural query errors.
- [x] Fixed after recheck: shared query validation covers literal types,
output-only fields, and strict content options.
- [x] Fixed after recheck: unavailable selected content returns an
exact-file diagnostic instead of a silent empty result.

## Tests and delivery

- [x] Full Builder suite: 2,707 tests.
- [x] Content Engine suite: 445 tests.
- [x] Asset uploader suite: 211 tests.
- [x] Project runtime/MCP targeted suite: 128 tests.
- [x] CLI/MCP suite: 1,051 tests.
- [x] Relevant package typechecks, lint, generated docs, and diff
checks.
- [x] Documentation impact reviewed and generated Content Engine/MCP
reference updated.
- [x] Add the missing cross-surface error matrix described above.
- [ ] Demonstrate red/green behavior for every new matrix case.
- [ ] Run the complete agent evaluation suite after explicit approval.
- [ ] Re-run all checks after the remaining checklist is implemented.

## Compatibility and risk

Normal query result and expression-binding shapes remain unchanged.
Diagnostics add optional fields. Existing public/pre-auth error
redaction remains intact. The remaining unchecked items are broader
exhaustive real-MCP coverage, parser-recovery limits, candidate-warning
labeling, cancellation coverage, and agent evaluations.
2026-09-04 16:00:28 +02:00
Oleg Isonen 0ffdf36665 fix: Explain dynamic Content Block asset sources (#6333)
## Summary

Content Block source operations now explain how to resolve a dynamic
Assets resource for one rendered occurrence. A caller can keep the
persisted expression, supply the concrete resource result through
`variables`, and receive safe structured diagnostics when the expression
does not resolve to a non-empty MDX Asset id.

The operation still does not load page routes or resource results
itself. That work remains in the rendering or caller context.

## Checklist

- [x] Reproduce the unresolved result-one Assets resource expression
- [x] Preserve the dynamic source expression while validating a concrete
Asset id
- [x] Return the evaluated value shape without exposing supplied values
- [x] Reuse the shared Content Block source resolver
- [x] Describe `renderScope` and `variables` in the operation schema
- [x] Add a result-one Assets resource example
- [x] Verify connection keeps the dynamic expression after resolving the
supplied result
- [x] Add regression coverage for structured unresolved-source
diagnostics
- [x] Regenerate runtime contracts and CLI documentation
- [x] Review fixture impact: fixture inputs and outputs are not affected
- [x] Review documentation impact: MCP and CLI guidance are updated

## Verification

- New diagnostic regression failed against the old generic error and
passed with this fix
- Project Build: 88 files, 2,284 tests passed
- Project Build typecheck
- CLI typecheck
- Generated documentation and contract checks
- Formatting and lint with warnings denied
- Full CLI suite from the implementation review: 66 files, 1,038 tests
passed

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

Closes #6330
2026-09-04 15:15:18 +02:00
Oleg Isonen 2e4efb52f9 docs: update Link target values (#6326)
## Summary

Update the Link documentation to match the current code.

The Builder uses `_self`, `_blank`, `_parent`, and `_top` as the Link
`target` values. The old docs described these as `Self`, `Blank`,
`Parent`, and `Top`, which could make setting a new-tab link confusing.

## Changes

- Document the four current target values.
- Explain that `_blank` opens a link in a new tab or window.
- Remove the outdated Target screenshot.
- Checked the rest of `docs/` for the same outdated target names; no
other related occurrences were found.

## Verification

- `git diff --check` passed.
- Confirmed the values in
`packages/sdk-components-react/src/__generated__/link.props.ts` and
`packages/sdk-components-react/src/create-link.tsx`.
- Checked that the docs file and navigation file exist.
- `pnpm exec oxfmt --check docs/university/core-components/link.md` did
not run because the Markdown file is excluded by the formatter
configuration.

## Checklist

- [x] Review the current Builder implementation.
- [x] Review the authoritative docs page.
- [x] Check for similar outdated documentation.
- [x] Review documentation impact: this PR updates the affected page; no
other docs need changes.
- [ ] Merge the pull request.
2026-09-03 20:23:42 +02:00
Oleg Isonen 75840a119c fix: Start previews and diagnose runtime failures (#6321)
## Summary

Preview startup now binds loopback directly instead of scanning network
interfaces first. MCP no longer exposes or accepts a fixed preview host
or port: the CLI asks the operating system for an available loopback
port and returns the resulting URL.

The CLI also returns safe, structured diagnostics when the runtime
blocks preview networking or Chromium IPC, when generated IDs collide,
and when a listening preview fails its HTTP readiness probe. Preview
startup retries only a genuine automatic-port race; HTTP and connection
failures return immediately instead of repeating the full startup
timeout five times.

## Issue checklist

- [x] #6318 — reproduced the `uv_interface_addresses` failure, replaced
interface discovery with a direct TCP bind, and added a regression test
for the exact failure.
- [x] #6319 — reproduced Chromium aborting before DevTools startup when
the managed runtime blocks browser IPC/socket operations. The host
restriction cannot be bypassed by the CLI, but future reports now
include a safe `browser_ipc_permission_denied` issue without raw stderr,
paths, or tokens.
- [x] #6320 — ran four reported insert variants sequentially across two
pages; all generated unique IDs. Future collision errors now include the
affected namespace and insertion stage without exposing the generated
ID.
- [x] #6324 — reproduced the generic readiness timeout with a real
generated preview whose root probe returned HTTP 500 after the server
printed its Local URL. The MCP contract and documentation also allowed
agents to force a busy port; network binding is now runner-owned.
- [x] #6325 — confirmed the responsive screenshot uses the same
readiness path. Future reports distinguish HTTP, refused-connection,
timeout, and permission failures, non-port failures are no longer
retried, and screenshots cannot request a fixed preview port.
- [x] Documentation impact reviewed. Updated MCP examples and vision
guidance to omit runner-owned host and port fields.

## Technical notes

- Removes the CLI's direct `get-port` dependency. That package calls
`os.networkInterfaces()` even when a host is supplied.
- Binds `{ host: "127.0.0.1", port: 0 }`, reads the assigned TCP port,
then closes the temporary server.
- Removes `host` and `port` from MCP preview and screenshot input
schemas and rejects those fields at runtime.
- Returns the selected preview URL from `preview.start`; managed
screenshots reuse that active target.
- Returns `PREVIEW_NETWORK_RESTRICTED` with a stable
`network_permission_denied` issue when the OS rejects loopback binding.
- Reads at most the last 4 KB of Chromium startup stderr, maps only the
reproduced IPC/socket denial to a safe diagnostic, and discards the
buffer after startup.
- Preserves browser startup issues through CLI browser fallback
aggregation and the MCP screenshot error boundary.
- Reports generated record collisions with `generated_id_conflict`, the
namespace constraint, and the fragment insertion stage.
- Preserves structured readiness diagnostics when preview output is
appended.
- Reports HTTP 5xx and local connection failures without exposing raw
fetch errors.
- Retries with a newly allocated port only for `EADDRINUSE` /
`PREVIEW_PORT_IN_USE`.

## Verification

- [x] New regression tests were shown to fail before implementation and
pass afterward.
- [x] Reproduced the old generic timeout through the real preview
controller; after the fix, the same path returned `PREVIEW_HTTP_ERROR`
with `http_status:500`.
- [x] Verified MCP advertised and accepted fixed host/port fields before
the change, then rejected both fields after the change.
- [x] Preview readiness/controller tests: 2 files, 29 tests passed.
- [x] Vision tests: 13 files, 81 tests passed.
- [x] Project-build tests: 88 files, 2,282 tests passed.
- [x] CLI tests: 66 files, 1,038 tests passed.
- [x] Vision, project-build, and CLI typechecks passed.
- [x] Lint passed with warnings denied.
- [x] Package-boundary and MCP documentation checks passed.
- [x] Fixture impact reviewed; no fixture inputs or generated fixture
outputs are affected. Generated documentation was updated; the generated
runtime operation contract did not change.

Agent evaluations were not run because they require separate approval.

Closes #6318

Ref #6319

Ref #6320

Ref #6324

Ref #6325
2026-09-03 16:34:20 +02:00
Oleg Isonen 7ff42abfb5 docs: Simplify MCP setup (#6311)
## Summary

- Add a short introduction with the only setup step: install the Codex
desktop app or Claude Desktop and ask it to install Webstudio CLI and
use MCP.
- Keep the existing generated reference below a divider.
- Remove documentation wording assertions across generators, CLI manuals
and help, MCP guidance, tool descriptions, and operation docs.
- Retain structured and executable coverage for command catalogs,
schemas, examples, runtime behavior, errors, and UI contracts.

## Implementation

The introduction lives in the MCP page template, and the generated
GitBook page is committed with it. Documentation prose is now free to
change without breaking tests; tests continue to cover machine-readable
contracts and observable behavior.

## Checklist

- [x] Add the concise introduction to the MCP template.
- [x] Regenerate the authoritative `docs/` page.
- [x] Audit the repository for documentation prose assertions.
- [x] Remove prose-only generator, manual, help, guidance,
schema-description, and operation-description assertions.
- [x] Preserve structured and executable contract coverage.
- [x] Review documentation impact: the existing MCP page is updated;
navigation and other documentation are unaffected.
- [x] Run targeted tests, typechecks, formatting, and lint checks.

## Verification

- CLI: 121 tests passed across the affected suites (119 in the sandboxed
run; 2 link tests passed separately with config-directory access).
- Project build: 513 tests passed.
- Protocol: 23 tests passed.
- Content engine reference: 1 test passed.
- `pnpm docs:mcp:typecheck`
- Typechecks for `webstudio`, `@webstudio-is/project-build`, and
`@webstudio-is/protocol`
- `oxlint --deny-warnings` on all changed TypeScript files
- `oxfmt` and `git diff --check`

## Compatibility and limitations

Documentation and test-only changes. No runtime behavior, schemas,
fixture inputs or outputs, or external integrations are affected.
2026-09-02 22:25:11 +02:00
Oleg Isonen 4a10645cb8 docs: Explain Builder themes and CSS warnings (#6298)
## Summary

- Document the shared System, Light, and Dark interface-theme preference
in the Dashboard and Builder guides.
- Distinguish the Webstudio interface theme from the authored site's
color scheme.
- Explain experimental, nonstandard, and obsolete CSS property warnings
in the Advanced Style panel.
- Add focused, matching light- and dark-theme screenshots of the
released Dashboard controls.

## Implementation

The Dashboard guide owns the screenshot and complete theme explanation.
The Builder anatomy guide documents the Builder menu path and links to
that canonical section. The screenshot pair was captured from the
released interface, cropped to the smallest useful region, and excludes
account identity.

## Checklist

- [x] Document the Dashboard theme path and available preferences.
- [x] Document the Builder theme path and canvas-theme boundary.
- [x] Explain CSS property status warnings and MDN compatibility
details.
- [x] Capture and visually review matching light/dark screenshots.
- [x] Review documentation impact and update all affected authoritative
`docs/` pages.
- [x] Verify internal anchors, asset references, image formats, and the
final diff.

## Verification

- `git diff --check` — passed.
- Screenshot assets — verified as 366×100 PNG files with equivalent UI
state and no account identity.
- Referenced files and `#interface-theme` / `#example-dark-mode` anchors
— verified.
- Repository formatter — not applicable; `oxfmt` excludes these
documentation targets.
- Tests — not run; documentation and screenshot assets only.
- Fixture inputs and generated outputs — unaffected.

## Known limitations

None.

Closes #6297
2026-09-01 01:06:21 +02:00
Oleg Isonen 9ec4c820e1 fix: Use writable npm cache for previews (#6272)
## Summary

- Use a writable `.webstudio/preview/.npm-cache` for npm preview
installs when `npm_config_cache` is not explicitly configured.
- Preserve explicit npm cache settings and pnpm behavior.
- Document a safe recovery path that requires no cache deletion.

This removes preview startup failures caused by an unwritable default
npm cache. Initial dependency installation and its network/download cost
still remain when preview dependencies are not already installed.

## Technical choices

- Keep the cache beside the isolated generated preview because that
directory is already writable and reused across regenerations.
- Let an explicit `npm_config_cache` continue to take precedence.
- Do not retry arbitrary install failures or classify package-manager
prose.

## Todo

- [x] Reproduce npm dependency installation against an unwritable cache
before changing code.
- [x] Reproduce #6274 at the same dependency-install stage used by
session-backed screenshots.
- [x] Demonstrate the regression assertion failing before the fix and
passing afterward.
- [x] Use the preview-local cache only for npm without an explicit
cache.
- [x] Preserve explicit npm cache configuration and pnpm behavior.
- [x] Verify the real dependency installer against an unwritable project
npm cache.
- [x] Verify session-backed screenshot flow proceeds from successful
preview preparation to browser capture.
- [x] Review documentation impact and update the CLI manual plus
generated MCP documentation.
- [x] Review fixture impact; no project fixture inputs or outputs are
affected.
- [x] Review the final diff for correctness, duplication, and
unnecessary complexity.

## Verification

- Actual npm install reproduced #6269 and #6274 with `EPERM` against an
unwritable cache.
- Real preview dependency installation passed after the fix while
project npm configuration still pointed at that cache.
- Fail-before/pass-after regression demonstrated.
- `pnpm --filter webstudio test` — 65 files, 1,044 tests passed.
- `pnpm --filter webstudio typecheck`
- `pnpm lint`
- `pnpm check:generated-docs`
- Formatting and diff checks passed.
- GitHub CI after rebasing onto current `main` — 30 checks passed, 7
skipped, no failures.
- Agent evaluations were not run because they require separate explicit
approval.

Closes #6269

Closes #6274
2026-08-31 19:12:01 +02:00
Oleg Isonen 1b48716ff4 docs: Document MDX-backed Content Blocks (#6273)
## Summary

- Document how people create, connect, edit, validate, and publish
MDX-backed Content Blocks.
- Document the complete MCP workflow, including scoped expression
sources, frontmatter updates, diagnostics, conflicts, and disconnect
behavior.
- Add two focused screenshots for the human workflow.
- Fix issues found by a low-reasoning MCP agent while executing every
documented workflow.

Ref #6182

## Implementation notes

- Connection confirmation uses the public `confirmReplacement` field
because `confirm` is reserved as a hidden transport field.
- MCP edits frontmatter with `update-content-block-frontmatter`;
materialized MDX elements are not persistent targets for generic
instance mutation tools.
- Reload now intentionally discards a conflicted local source and
accepts the Asset's latest storage revision before an edit is reapplied.
- Generated MCP documentation and runtime contracts are updated from
their source files.

## Checklist

- [x] Document the human MDX Content Block workflow.
- [x] Document MCP discovery, connection, editing, diagnostics,
frontmatter, dynamic sources, conflicts, and disconnection.
- [x] Add only screenshots that clarify otherwise hard-to-describe UI
steps.
- [x] Test every documented MDX capability through the local MCP CLI
with a low-reasoning agent.
- [x] Reproduce and fix the replacement-confirmation schema mismatch.
- [x] Reproduce and fix persistent-session conflict reload and reapply.
- [x] Add regression tests that failed before each product fix and pass
afterward.
- [x] Regenerate runtime contracts and generated MCP documentation.
- [x] Review authoritative `docs/` content and update affected pages.
- [x] Run affected tests, typechecks, formatting, and diff checks.

## Verification

- Project Build: 208 affected tests passed.
- Content Engine Asset session: 8 tests passed.
- CLI documentation tests: 14 tests passed.
- MCP docs generator: 3 tests passed.
- Content Engine, Project Build, CLI, and MCP docs typechecks passed.
- `pnpm oxfmt --check` passed.
- `git diff --check` passed.
- Low-reasoning MCP evaluation passed all documented workflows,
including a real two-client revision conflict, reload, reapply, and
final saved inspection.
2026-08-31 18:39:05 +02:00
Oleg Isonen f6c524572d feat: add Builder dark theme and rebuild colors
## Summary

Rebuilds Webstudio’s color system, migrates the entire Builder to it,
and introduces a complete dark theme.

A small set of theme colors and contrast controls now drives the
interface through relative CSS colors:

```text
Theme colors and contrast
    ↓
Light/dark derivation bounds
    ↓
Derived color families
    ↓
Reusable semantic colors
    ↓
Component composition
    ↓
Builder interface
```

CSS is the runtime source of truth. Light and dark modes share the same
theme inputs, so changing a theme color automatically updates its
surfaces, content, borders, states, and component presentation. The
refactor turns dark mode into a first-class Builder theme rather than a
separately maintained palette.

This is an atomic migration. Legacy color names and compatibility
aliases will not remain after all consumers have moved.

## Scope

- The color source, derivation model, semantic vocabulary, validation,
typing, and Storybook overview.
- Every design-system component.
- Builder, dashboard, authentication, shared UI, Query Builder, stories,
and tests that consume the legacy color scale.
- Removal of the legacy Stitches color scale after its final consumer is
migrated.
- A complete dark theme across application UI while keeping authored
canvas content independent.
- Complete light and dark visual verification.
- Unified browser-test infrastructure: Vitest Browser Mode for Design
System browser contracts and standard Playwright Test for all Builder
E2E workflows.

Literal colors remain only where color is the actual content or belongs
to a deliberately separate system, such as user-authored values,
external previews, brand artwork, data visualization, functional canvas
overlays, and source assets. Every remaining exception must have an
explicit owner.

## Architecture decisions

- Theme authors control six color families and three bounded contrast
controls.
- Relative CSS colors derive the rest of the system.
- Dark mode changes derivation bounds instead of maintaining a second
independent palette.
- Components consume a small reusable semantic vocabulary.
- Component states and exceptional presentation are composed inside
components.
- Builder-specific categorical meaning uses focused composite recipes,
not intent semantics or a larger global vocabulary.
- TypeScript validates public semantic CSS-variable names but does not
generate runtime colors.
- No token-manager dependency, compatibility aliases, or
component-specific public color tokens.
- A new semantic or theme variable is added only when existing semantics
and local composition cannot represent a reusable cross-feature
decision.

## Todo

### Color-system foundation

- [x] Establish direct CSS as the runtime source of truth.
- [x] Define bounded theme colors and contrast controls.
- [x] Derive light and dark modes from the same theme inputs.
- [x] Reduce the semantic inventory to reusable decisions and explicit
accessibility pairs.
- [x] Generate only TypeScript names for public semantic CSS variables.
- [x] Validate layers, references, cycles, consumption, naming,
generated types, and browser-computed contrast.
- [x] Add the Storybook color-system overview and theme controls.
- [x] Document the architecture and its relationship to the public Craft
specification.

### Design-system migration

- [x] Replace every active legacy color consumer in design-system
components and Storybook chrome.
- [x] Compose component states locally without component-specific public
tokens.
- [x] Preserve intentional preview and sample-content colors.
- [x] Add complete positive, negative, warning, informative, and accent
accessibility pairings.
- [x] Validate light and dark scheme-bound references independently.
- [x] Verify the design-system package with typecheck, lint, formatting,
Storybook, and 222 tests.

### Inventory and contract

- [x] Inventory all 319 legacy references across 107 Builder, Query
Builder, story, test, and design-system files.
- [x] Classify every reference as a direct semantic replacement, local
composite recipe, documented literal exception, or removable code.
- [x] Record the intentional authored-content, external-preview,
functional-preview, data-visualization, brand, and source-asset
exceptions.
- [x] Treat the public semantic vocabulary as closed by default and
justify any proposed addition by cross-feature reuse and accessibility
need.
- [x] Keep Builder code limited to typed public variables accepted by
`cssVar()`.

### Theme infrastructure

- [x] Add `system` (default), `light`, and `dark` application
preferences.
- [x] Persist the preference in existing client settings and resolve
`system` through `matchMedia`.
- [x] Apply the resolved scheme before paint and respond to
operating-system changes.
- [x] Expose the persisted theme control under **View → Color scheme**.
- [x] Keep authored canvas documents independent from the application
theme.
- [x] Ensure portals, overlays, embedded surfaces, and third-party
controls receive the correct interface scheme.

### Generic interface migration

- [x] Migrate shared dialogs, project settings, notifications, controls,
and Query Builder.
- [x] Migrate dashboard, authentication, publishing, and shared
application UI.
- [x] Migrate the Builder loading state, topbar, sidebars, navigator,
workspace, footer, settings, pages, alerts, and menus.
- [x] Replace fixed interface literals alongside their owning surfaces.
- [x] Remove the old scrollbar variable and hardcoded loading color.

### Style panel and style-source recipes

- [x] Migrate ordinary style-panel inputs, labels, layouts, spacing,
alignment, menus, property states, and value editors.
- [x] Model style-source provenance, kind, interaction, and error as
separate axes.
- [x] Compose categorical style-source colors from neutral and
accent-relative colors.
- [x] Use negative semantics only for actual errors, not overwritten
provenance.
- [x] Reuse one feature-owned recipe across style-source controls,
badges, menus, and values without adding global tokens.

### Canvas and editors

- [x] Migrate canvas selection, reusable outlines, grid guides, resize
handles, labels, badges, and overlays.
- [x] Keep collaborator colors as data visualization while guaranteeing
readable labels.
- [x] Prevent Builder theme values from leaking into authored content.
- [x] Migrate editor surfaces, selections, autocomplete, lint states,
and tooltips.
- [x] Derive syntax visualization from neutral and accent-relative
colors; reserve intent colors for diagnostics.

### Explicit color domains

- [x] Isolate external search and social previews from the application
scheme.
- [x] Preserve user-authored CSS values, functional color previews, and
checkerboards.
- [x] Review brand surfaces independently in both schemes.
- [x] Document every retained literal in the exception ledger.

### Stories, tests, and legacy removal

- [x] Migrate all affected Builder stories and tests.
- [x] Replace the design-system test that intentionally references the
legacy scale.
- [x] Add theme-preference and scheme-resolution behavior tests.
- [x] Remove the legacy `design-tokens.color` registry.
- [x] Remove the Stitches `colors` scale and `rawTheme.colors`.
- [x] Remove color-name conversion code that exists only for the legacy
scale.
- [x] Add a focused repository check that rejects legacy references.
- [x] Confirm no compatibility mappings or deprecated aliases remain.

### Browser-test infrastructure

- [x] Run pure Design System tests in Node and browser-dependent tests
in Vitest Browser Mode with Playwright Chromium.
- [x] Migrate all 79 Builder E2E workflows from the custom harness to
standard Playwright Test.
- [x] Replace filename-based shard discovery with standard Playwright
sharding.
- [x] Preserve clean-backend shard retries and upload Playwright failure
artifacts in CI.
- [x] Remove the custom runner, registry, and timeout infrastructure.

### Verification and documentation

- [x] Compare light-mode visual regressions directly with `main`; review
every difference rather than accepting snapshots wholesale.
- [x] Add representative dark coverage for dashboard, Builder shell,
style panel, dialogs, editors, canvas tools, and status states.
- [x] Test default and extreme custom-theme presets.
- [x] Verify semantic and categorical-recipe contrast.
- [x] Test first paint, persistence, system changes, portals, and canvas
isolation.
- [x] Run affected tests, typechecks, lint, formatting, and Storybook
checks.
- [x] Review fixture impact; no fixture inputs or generated outputs are
affected.
- [x] Review authoritative `docs/` content and update it where needed.
- [x] Review the final diff and make this checklist reflect delivered
scope.

## Current verification

- Design-system typecheck and generated-type freshness: passed.
- Design-system tests: 222 passed, including the full theme-parameter
contrast sweep and browser component coverage.
- Builder typecheck and production build: passed.
- Theme preference and scheme-resolution tests: 10 passed.
- Scoped Oxlint, formatting, and diff checks: passed.
- Design-system Storybook build and representative light/dark visual
review: passed.
- Builder E2E: 80/80 Playwright entries passed (setup plus 79
workflows).

Ref #3818
Closes #6267
2026-08-30 03:24:56 +01:00
Oleg Isonen f9681dc30f feat: MDX-backed Content Blocks (#6234)
## Summary

Supersede #6182 with a clean implementation of MDX-backed Content Blocks
on top of the unchanged `main` Content Block editing model. A connected
`.mdx` Asset changes where authored children are loaded and saved; it
must not change how Content mode inserts, edits, selects, moves, copies,
or deletes instances.

The previous implementation remains available in
`agent/mdx-content-block-6124` and
`agent/mdx-content-block-6124-review`. Reuse its independently tested
parsers, converters, schemas, Asset repository, template resolution,
publication, MCP, and UI work where those pieces fit the corrected
architecture. Do not port its per-operation storage routing, projected
editing stores, MDX undo/history, or atomic multi-root machinery.

Ref #6124
Ref #6182
Closes #6261

## Locked behavior

- `.md` keeps its existing Markdown behavior; `.mdx` enables MDX.
- Standard Markdown structures are stored as Markdown. JSX is used only
for structures Markdown cannot represent losslessly.
- Generic elements use `<ws.element ws:tag="…">`; template instances use
`<ws.element ws:name="…">`.
- Invalid, unsafe, or unsupported MDX never prevents editing the file or
publishing the project. Builder and publication use the same recovery
result: recoverable nodes are skipped with source-located warnings,
while an unrecoverable document renders no authored file content in
either environment.
- Connecting a source never serializes existing Content Block children
into the file. **Connect** removes ordinary persisted children,
preserves Templates, sets `src`, and uses file content.
- Disconnecting resets `src`, discards derived instances, leaves the
Content Block empty, and never changes the Asset.
- While connected, normal Content Block mutations update in-memory
instances synchronously. Asset persistence happens afterward and never
blocks rendering.
- Genuine external revision conflicts use the standard reload-required
flow. Rapid local edits are ordered/coalesced and must never conflict
with each other.
- Repeated scopes use Content Block ID plus Collection item identity.
- Multiple Content Blocks may intentionally share one MDX Asset. They
share one revisioned document session and edits propagate to every
connected occurrence.
- Missing templates show warnings/placeholders only in Builder and are
skipped on published sites.

## Reuse strategy

- [x] Audit all 62 original implementation commits and 5 later
review/fix commits
- [x] Reuse the shared Content Engine Markdown/MDX AST, parser,
serializer, diagnostics, reference discovery, and tests
- [x] Reuse SDK source schemas, instance-name utilities, unique template
naming, and source-integrity validation
- [x] Reuse template resolution/materialization and authored-tree
conversion after removing storage-operation coupling
- [x] Reuse revisioned Asset content repository, authorization, limits,
and browser/server transport
- [x] Reuse publication, document-graph, CLI, MCP, source-control UI,
frontmatter UI, and E2E work where contracts remain valid
- [x] Reuse tests as behavioral specifications; rewrite tests that
assert the rejected projection/routing architecture
- [x] Do not port per-operation MDX routing, alternate projected
mutation stores, semantic reconciliation after each command, MDX Builder
history/undo, atomic multi-file rollback, or canvas loading/saving UI


## Old-branch porting map — do not rewrite

Before implementing any checklist batch, inspect the named
implementation and tests on `agent/mdx-content-block-6124-review`. Move
the implementation and its tests first; change only the boundary that
conflicts with the rebuilt architecture. Do not create a second
implementation under a new name. When a test encodes the rejected
projected-store architecture, retain its behavioral assertion and
replace only its setup.

### Already ported

- `parseMarkdownAst`, `createMdxDocumentFromMarkdown`,
`parseMdxDocument`, `serializeMdxDocument`, `preferMarkdownSyntax`,
`discoverMdxBodyAssetReferences`, `discoverMdxAssetReferences`, and
`previewMarkdownToMdxConversion`
- Asset-reference, byte-stream, document-graph, Markdown, and MDX
helpers included in commit `76bc388ba9`
- Tests: the corresponding `packages/content-engine/src/**/*.test.ts`
files already on this branch

### Port with their existing tests

| Area | Functions to move or adapt | Tests to move with them |
| --- | --- | --- |
| Source contract and integrity | `parseContentBlockSourceProp`,
`getContentBlockSource`, `isEqualContentBlockSource`,
`formatContentBlockSourceIntegrityIssue`,
`getContentBlockSourceIntegrityIssues`,
`allocateUniqueContentBlockTemplateName` |
`packages/sdk/src/content-block.test.ts`,
`packages/sdk/src/schema/content-block.test.ts` |
| Existing instance/name utilities | `findChildReferenceIndex`,
`findParentInstanceReference`, `getInstanceName` |
`packages/sdk/src/instances-utils.test.tsx` |
| Template naming in mutations |
`assignUniqueBlockTemplateNamesMutable`,
`findBlockTemplateNameCollision`, `getSourceBackedBlockTemplateContext`,
`getBlockTemplateNameConfirmation`, `getBlockTemplateInsertionIndex` |
`packages/project-build/src/runtime/block.test.ts` |
| Content-mode property permissions | `getContentModePropEligibility`;
preserve its distinction between editable, design-only, incompatible,
and unknown props |
`packages/project-build/src/runtime/content-mode-permissions.test.ts` |
| Template resolution | `resolveMdxTemplates` |
`packages/project-build/src/runtime/mdx-template-resolution.test.ts` |
| Template materialization | `createMdxScopeIdGenerator`,
`materializeMdxTemplates` |
`packages/project-build/src/runtime/mdx-materialization.test.ts` |
| MDX → instances and instances → MDX |
`isMaterializedMdxAuthoredContentRoot`, `preserveMdxAuthoredContentIds`,
`materializeMdxAuthoredContent`, `reconcileMdxAuthoredContent`,
`omitTransientEmptyParagraphs`, `serializeMdxTemplateInsertion` |
`packages/project-build/src/runtime/mdx-authored-content.test.ts`;
retain the pure serialization cases from `mdx-storage-adapter.test.ts` |
| Revisioned Asset I/O | `createAssetContentRevision`,
`readAssetContentBytes`, the package-owned
`createHttpAssetContentRepository`, the thin Builder
`createBuilderHttpAssetContentRepository`, `createAssetContentBridge`,
`initAssetContentBridge`, `getAssetContentBridge`; keep one repository
implementation rather than copying the old Builder duplicate |
`packages/asset-uploader/src/content-repository.test.ts`,
`apps/builder/app/builder/shared/assets/mdx-content-repository.test.ts`,
`apps/builder/app/shared/asset-content-bridge.client.test.ts` |
| Source lifecycle planning | `createContentBlockBodyRemoval`,
`prepareContentBlockConnect`, `prepareContentBlockDisconnect`,
`prepareContentBlockSwitch`, `ContentBlockSourceRevisionConflictError` |
`packages/project-build/src/runtime/content-block-source-lifecycle.test.ts`
|
| Per-Asset revision/session behavior | `getContentBlockSessionSource`,
`isContentBlockSessionSourceCommitted`; reuse the loading,
authorization, revision, queue, conflict, retry, cancellation, and
source-replacement internals of `createMdxAssetEditingSession`, but key
the rebuilt session by Asset rather than Content Block/render root |
Port the applicable cases from
`packages/project-build/src/runtime/mdx-asset-session.test.ts`; replace
root-projection setup with in-memory snapshot setup |
| Template migrations | `validateMdxTemplateMigrationConfirmation`,
`planMdxTemplateMigration` |
`packages/project-build/src/runtime/mdx-template-migration.test.ts` |
| Publication | `createPublishedMdxMaterializationCache`,
`materializePublishedMdx`, `getPublishedMdxContentDatabaseMaxBytes`,
`getDynamicPublishedMdxSourceBlockIds`, `hasDynamicPublishedMdxSources`,
`createPublishedBuildContentCompilationPlan`,
`resolvePublishedMdxDependencyClosure`,
`resolvePublishedMdxAssetCandidates` |
`packages/project-build/src/runtime/published-mdx.test.ts`,
`packages/project-build/src/content-database.test.ts` |
| Build/import integrity | `assertContentBlockSourceIntegrity` and the
Content Block cases in
`getBuildIntegrityIssues`/`formatBuildIntegrityIssue` |
`packages/project-build/src/build-integrity.test.ts` |
| MDX paste/MCP insertion | `insertMdxText` and its input/result
schemas; retain parsing, confirmation, template-resolution, and
insertion-index logic, then invoke the normal mutation path |
`packages/project-build/src/runtime/mdx-paste.test.ts` |
| Builder source control | `createContentBlockSourceController`,
`createBuilderContentBlockSourceController` and the existing
source-control components | `content-block-source-controller.test.ts`,
`content-block-source.test.tsx` |
| Diagnostics | `formatContentBlockDiagnostic`,
`deduplicateContentBlockDiagnostics`, `takeNewContentBlockDiagnostics` |
Port only their focused cases from
`apps/builder/app/shared/content-block-content.test.ts` |

### Explicitly do not port

- `projectContentStorageChanges`, `createContentStorageProjection`,
`createContentStorageSelectorProjection`,
`executeContentStorageMutation`, `executeContentStorageTextReplacement`,
`executeContentStoragePropMutation`, and
`executeContentStorageStructuralMutation`
- `applyMdxContentStorageChanges` and `prepareMdxContentStorageWrites`;
replace them with one post-mutation snapshot serializer built from the
reusable pure `reconcileMdxAuthoredContent` converter
- `createContentBlockApplicationOperations` and the persistence-plan
wrappers that route individual editing commands
- Builder `$runtime*` projected stores, materialized mutation functions,
alternate selector/tree helpers, pending projected-change state, and
MDX-specific mutation sequences from `content-block-content.ts`
- Canvas loading/saving/open-file presentation state, MDX history/undo,
and atomic rollback machinery

## Implementation checklist

### 1. Preserve the `main` editing architecture

- [x] Keep normal synchronous insert, text, Enter, slash menu, property,
delete, move, wrap, copy/paste, selection, and focus mutations unchanged
- [x] Materialize connected content through the same runtime instance
interfaces used by ordinary Content Blocks
- [x] Keep connected authored children in memory and exclude them from
persisted build data
- [x] Route persistence only after a synchronous mutation has updated
the canvas
- [x] Never wait for Asset loading, conversion, or saving before
rendering an interaction
- [x] Add no MDX-specific implementation to individual editing commands

### 2. Shared Markdown and MDX conversion

- [x] Parse Markdown and the Markdown portion of MDX through one shared
AST and plugin configuration
- [x] Implement one shared MDX-to-instances converter and one shared
instances-to-MDX converter
- [x] Serialize headings, paragraphs, links, images, lists, tables, task
lists, blockquotes, thematic breaks, code, emphasis, strong,
strikethrough, and other supported GFM structures as Markdown
- [x] Serialize only non-lossless/custom structures as Webstudio JSX
- [x] Preserve nested Markdown in MDX elements, frontmatter, comments,
tight/loose list semantics, code whitespace, reference links, and Asset
references
- [x] Omit transient empty draft paragraphs instead of emitting
`<ws.element ws:tag="p" />`
- [x] Preserve unsupported source text and authored metadata whenever it
cannot be converted safely
- [x] Add deterministic round-trip and canonical-output coverage for
every supported Markdown structure

### 3. Invalid and unsafe MDX

- [x] Preserve file source exactly; never silently repair or rewrite
invalid source
- [x] Ignore imports, exports, expressions, executable JavaScript,
invalid properties, and other unsupported nodes when they can be
isolated
- [x] Continue rendering valid siblings and report every skipped subtree
once with filename, line, and column
- [x] Use the same recovery pipeline in Builder and publication so both
render the same authored result for the current file revision
- [x] When structural syntax prevents recovery, render no authored file
content in Builder or publication instead of retaining a stale
materialization
- [x] Skip an unrecoverable Content Block on published sites and
continue the build with a warning
- [x] Keep invalid and unsafe files editable and reconnectable
- [x] Enforce UTF-8, byte-size, depth, node-count, property-count, and
generated-output limits with source-located diagnostics where possible

### 4. File formats and Asset creation

- [x] Keep `.md` parsing and raw-HTML behavior unchanged
- [x] Support creating, opening, editing, deleting, and recreating
`.mdx` files through the normal text-file UI
- [x] Open `.mdx` in the same editor experience as `.md`
- [x] Always create an independent empty MDX Asset even when identical
empty files exist
- [x] Allow renaming `.md` to `.mdx` without converting or rewriting its
contents; after rename, handle invalid contents through the normal MDX
diagnostics and recovery pipeline
- [x] Update an already-open MDX editor immediately after canvas
persistence
- [x] Bound read/write buffering and verify the returned Asset identity
and revision

### 5. Source contracts and lifecycle

- [x] Support direct Asset bindings and dynamic expression bindings
- [x] Keep source mutation controls available only in Design mode; show
the connected filename and **Open** action read-only in Content mode
- [x] Reject duplicate source props, unsupported prop types, missing
Assets, non-MDX Assets, and unauthorized Assets
- [x] Analyze selected file diagnostics before confirmation without
modifying the block or file; MDX syntax diagnostics do not block
connection
- [x] For a non-empty block, show **Abort** and **Connect** and explain
that ordinary children will be replaced
- [x] On connect, remove every non-template child, preserve Templates,
set `src`, and materialize file content
- [x] Replace disconnect handoff with normal `src` reset; discard
derived content and leave the block empty
- [x] Remove disconnect confirmation and conversion; resetting a
repeated binding leaves every occurrence empty
- [x] Switching sources validates/loads the target before replacing the
usable binding and leaves both files unchanged
- [x] Revalidate Asset revision and project version when confirmation is
submitted
- [x] Make repeated connect/disconnect operations idempotent
- [x] Prevent disposed, stale, or out-of-order loads from replacing the
newest source
- [x] Keep loading, saving, conflict, empty-file, and open-file actions
off canvas

### 6. Asynchronous persistence and conflicts

- [x] Snapshot the current in-memory authored root after normal
mutations
- [x] Serialize/coalesce rapid edits per resolved Asset and always
prepare against the latest local snapshot
- [x] Allow independent Assets to save concurrently without scope
leakage
- [x] Use one revisioned document session and one ordered save queue per
Asset, even when multiple Content Blocks or render scopes reference it
- [x] After a shared Asset changes, rematerialize every connected
occurrence without persisting their generated instances to project data
- [x] Pin the resolved Asset and exact loaded revision for each write
- [x] Recheck authorization immediately before storage
- [x] Ensure local queued edits never produce stale-revision conflicts
with each other
- [x] On a genuine external revision conflict, preserve local content,
stop writes, and enter the standard reload-required state
- [x] Keep failed non-conflicting writes retryable without discarding
local content
- [x] Finish already accepted persistence writes after a Content Block
unmounts or switches source
- [x] Cancel obsolete loads, materializations, and UI updates after
unmount, reload, or source switch
- [x] Dispose project-scoped roots, queues, parse caches, failure state,
generations, and session subscriptions when the Asset session is
replaced
- [x] Invalidate Asset resources and synchronize open file editors after
successful writes
- [x] Keep multi-root persistence ordered, serial, and non-atomic;
report partial completion without rollback
- [x] Do not add Asset-history undo/redo; native text-editor undo
remains local to the open editor

### 7. Content mode parity

- [x] Empty connected blocks remain selectable and expose the normal
Content-mode `+` affordance
- [x] First insertion renders immediately
- [x] Enter creates and focuses the next paragraph immediately
- [x] Slash commands work in newly created empty paragraphs
- [x] Deleting one selected instance deletes exactly that instance
- [x] Mouse and keyboard moves use the same operation and permissions
- [x] Preserve sibling reorder, nested reparenting, and moves between
editable Content Blocks where `main` permits them
- [x] Preserve content-editable props such as Link `href`
- [x] Preserve copy, paste, cut, multi-cut, wrapping, unwrapping, grid
insertion, selection, Navigator order, and canvas order
- [x] Cover mutation routing centrally, retain the full unconnected
Builder suite, and exercise representative connected interactions in
real Builder E2E

### 8. Template names and permissions

- [x] Resolve only exact user-facing names from the direct flat
Templates list
- [x] Enforce unique names within each Templates list
- [x] Reject duplicate names during rename with inline validation
- [x] Allocate unique names during insert, paste, duplicate, move, wrap,
and unwrap
- [x] Apply valid template properties using the same permissions as
Content mode
- [x] Preserve and warn about ignored design-only, invalid, or stale
properties
- [x] Preserve template shell provenance without serializing expanded
template internals
- [x] Generate deterministic materialized IDs from source identity,
render scope, and authored usage path so focus/selection survives saves
- [x] Warn before renaming or deleting referenced templates; files
remain unchanged
- [x] Provide shared AST-based rename/remove migration operations for
MCP-assisted multi-file cleanup

### 9. Scope, frontmatter, and diagnostics

- [x] Scope materialized content by Content Block ID plus exact
Collection-item/render context
- [x] Prevent repeated instances and dynamic candidates from leaking
generated IDs, template resolution, or diagnostics across scopes while
sharing the underlying Asset source and revision where applicable
- [x] Remove frontmatter editing from Settings; edit the exact YAML
through the MDX Asset file editor
- [x] Preserve the exact frontmatter source byte-for-byte during canvas
body saves
- [x] Route canvas body changes and complete-file editor saves through
one revisioned per-Asset coordinator
- [x] Show a source warning indicator in Settings and precise
source-located diagnostics in the Asset editor
- [x] Deduplicate repeated diagnostics without losing distinct locations
- [x] Keep unresolved-template placeholders selectable and accessible
only in Builder

### 10. Asset references, transfer, and integrity

- [x] Discover and rewrite Asset references in frontmatter, Markdown
links/images, and MDX properties
- [x] Preserve Asset references while editing unrelated content
- [x] Include referenced Asset and template revisions in
dependency/cache identity
- [x] Route referenced Assets through published URL rewriting
- [x] Validate Content Block source integrity during project import,
fragment/page transfer, copy/paste, build integrity, and pre-publish
audit
- [x] Allow multiple Content Blocks and dynamic render scopes to
reference the same MDX Asset without treating the connection as an
integrity error
- [x] Preserve Templates when copying a connected Content Block
- [x] Preserve direct and dynamic Content Block source bindings when
copying or pasting within the same project
- [x] When copying only one rendered Collection item, resolve that
item’s current MDX Asset and replace the copied Content Block’s dynamic
source with a direct binding to that same Asset
- [x] When copying an entire Collection, preserve the Collection, its
instances, and its dynamic source bindings
- [x] Preserve the complete MDX source and frontmatter by reference;
copying a connected block must not rewrite the shared file
- [x] During cross-project transfer, copy every discoverable distinct
bound MDX Asset and rewrite transferred bindings to the destination
Assets
- [x] If some cross-project dynamic candidates cannot be discovered or
copied, continue with successful copies, preserve what can be
represented safely, and warn about skipped candidates
- [x] Revalidate source revisions and the destination project before
applying a cross-project transfer
- [x] If cross-project Asset creation or project insertion fails, leave
the source project unchanged and clean up any newly reserved destination
Asset
- [x] Use the same transfer operation through Builder and MCP

### 11. Dynamic sources and publication

- [x] Resolve dynamic bindings once per render/save scope
- [x] Require finite, discoverable candidate sets for publication and
load no unrelated MDX
- [x] Warn and skip unbounded, missing, unreadable, malformed, or unsafe
sources without failing the complete build
- [x] Materialize only rendered Content Blocks; ignore Templates
definitions and `src` on non-Content-Block instances
- [x] Skip unresolved template subtrees while keeping valid siblings
- [x] Include selected template styles, fonts, resources, unresolved
names, and referenced Asset revisions in generated dependency output
- [x] Keep draft-only MDX Assets out of publication retention

### 12. Builder, MCP, CLI, and shared operations

- [x] Use the same converters, source lifecycle, revision checks,
persistence service, diagnostics, and template migrations in Builder and
MCP
- [x] Keep frontmatter editing file-based; retain whole-frontmatter
runtime operations only where compatibility requires them
- [x] Avoid Builder-only or MCP-only mutation/conversion implementations
- [x] Regenerate runtime/API contracts from their source definitions
- [x] Update CLI Content Engine reference and generated MCP
documentation

### 13. Verification and delivery

- [x] Add regression tests that reproduce failures from the previous
implementation, including double deletion and blocked rendering
- [x] Cover two Content Blocks sharing one Asset: edits from either
block serialize once, update both canvases, preserve scope-specific
Templates, and cannot overwrite a newer local snapshot
- [x] Verify new tests fail against the broken implementation and pass
with the corrected behavior
- [x] Run SDK, Content Engine, Asset Uploader, Project Build, Builder,
React SDK, CLI, Protocol, and HTTP Client affected suites and typechecks
- [x] Run real Builder E2E for source lifecycle, live file refresh,
empty insertion, Enter/slash focus, Link props, delete, keyboard
reorder, switching, disconnect, conflicts, unresolved templates, dynamic
sources, and repeated scopes
- [x] Regenerate shared project fixture bundles and verify a second
generation leaves the tree clean
- [x] Run lint, package boundaries, `pnpm oxfmt --check`, and `git diff
--check`
- [x] Review and update authoritative `docs/` content and navigation
- [ ] Run the complete agent evaluation suite — not run because it
requires separate explicit approval

### 14. Content-mode parity and disconnect handoff

- [x] Allow keyboard reordering in Content mode through the normal move
command while keeping the editable Content Block boundary
- [x] Let already accepted Asset writes drain independently before
resetting the source; disconnect does not load, flush, or convert the
file
- [x] Remove MDX-to-instance copying from disconnect
- [x] Reset `src`, remove the derived tree, and leave the ordinary
Content Block empty
- [x] Preserve automatic text-editor focus after inserting editable
content from the template menu
- [x] Remove Builder frontmatter-save UI and report complete-file save
failures through the Asset editor
- [x] Add regression coverage for keyboard movement, disconnect
teardown, source switching, insertion focus, and paragraph deletion
focus restoration
- [x] Run the complete six-scenario MDX Content Block Builder E2E suite

### 15. Final architecture review fixes

- [x] Keep one canonical ordered pending document per Asset so shared
Content Block occurrences cannot conflict with one another
- [x] Merge ordered local edits from different occurrences while
preserving text changes, insertions, deletions, and sibling reordering;
keep external revisions on the strict reload-required path
- [x] Centralize typed render-scope parsing, repeated-occurrence
detection, scoped variable lookup, and dynamic Asset resolution across
Canvas, Settings, source lifecycle, and copy/paste
- [x] Capture the owning Asset session when a save is accepted so normal
unmount/source-switch teardown cannot redirect or lose it
- [x] Dispose project-owned roots, queues, caches, generations, failure
state, and subscriptions when replacing the project Asset session
- [x] Add regression coverage for same-Asset edits from two occurrences,
same-node last-write ordering, structural reorder, insertion
preservation, scoped source resolution, and project cleanup
- [x] Re-run affected full unit suites, typechecks, production build,
static checks, and all six MDX Content Block Builder E2E scenarios


### 16. Final persistence and transfer hardening

- [x] Drain every accepted per-Asset edit before project-session
teardown while retaining the owning authorization context
- [x] Drain canvas serialization queues and revisioned Asset writes
before pre-publish audit and publication
- [x] Block publication on a queued serialization failure or Asset
revision conflict
- [x] Copy actual Asset bytes during cross-project paste, preserve MDX
reference names, rewrite direct and Collection-backed Asset IDs, and
clean up newly created Assets when insertion fails
- [x] Include every resolved dynamic MDX source when copying an entire
Collection and warn while preserving successful copies when individual
Assets cannot be transferred
- [x] Keep MDX editor save failures visible with retry and route
conflicts through the standard reload-required flow
- [x] Give keyboard nested reparenting the same editable Content Block
boundary used by pointer movement
- [x] Add regression coverage and rerun the complete Content Engine and
Builder unit suites plus type, lint, formatting, package-boundary, and
diff checks

## Current rebuild status

- The connected Content Block now owns one complete MDX document:
frontmatter feeds designed-shell bindings and the explicit Body outlet
owns the authored MDX body.
- Builder and published output resolve the same frontmatter document
graph, including nested document references and Asset references.
- Direct frontmatter text/property edits use the existing ordered
per-Asset session; referenced-document values remain file-owned.
- Repeated Collection occurrences keep isolated runtime shell/Body
identities and occurrence-specific document values.
- The local blog article layout has been upgraded to the document/Body
model and its bindings validate cleanly.
- Full affected package suites and typechecks pass; every new regression
was demonstrated fail/pass.
- `pnpm lint`, package boundaries, `pnpm oxfmt --check`, generated
API/docs checks, fixture builds, and `git diff --check` pass.
- Full fixture synchronization is blocked by the development API
rejecting this local CLI version before fixture generation; existing
fixture bundles build successfully.
- Complete agent evaluations remain pending explicit approval.

### 17. Treat Content Block source as a property

- [x] Declare Content Block `src` in component metadata as a Design-mode
initial property labeled **Source**
- [x] Add the short property description: “Create an .mdx file in
Assets, then select or bind it here.”
- [x] Accept only a direct Asset or an expression resolving to an Asset
ID; reject URL/string sources, missing Assets, and non-MDX Assets
- [x] Extract and reuse the Image-style Asset picker and binding
presentation without duplicating source-control UI
- [x] Render Source through the normal properties section and remove the
separate source-section placement and special `src` filtering
- [x] Route Source changes through Content Block connect, switch, and
disconnect lifecycle operations instead of generic prop updates
- [x] Keep connect confirmation, Open, diagnostics, and persistence
state attached to Source; remove frontmatter and disconnect UI
- [x] Add regression coverage for direct Assets, dynamic Asset bindings,
invalid resolved values, lifecycle operations, and Design/Content mode
visibility
- [x] Re-review authoritative `docs/` content for the Source-property UI
change and update it if required

### 18. Source property UX

- [x] Show one full-width bindable Connect .mdx file button while
disconnected
- [x] Show equal-width filename and Open buttons while connected
- [x] Switch files from the filename button through the source lifecycle
- [x] Override PropertyLabel reset and Alt-click with the disconnect
lifecycle
- [x] Preserve confirmation, pending, failure, repeated-scope, and
dynamic-source behavior
- [x] Add regression coverage for connect, switch, binding, reset, and
disconnect
- [x] Update Content Block documentation
- [x] Run affected tests, typecheck, lint, formatting, package
boundaries, and diff checks


### 19. Reduced file-owned frontmatter follow-up

This checklist supersedes the earlier structured-frontmatter and
disconnect-copy behavior. Frontmatter remains file-owned and is edited
only in the MDX Asset editor.

#### A. Remove structured frontmatter UI

- [x] Remove the JSON frontmatter editor and unused Builder
controller/state
- [x] Keep source mutation Design-mode-only; show connected Source
read-only in Content mode and allow authorized editors to open the
complete file
- [x] Retain runtime frontmatter APIs only where compatibility requires
them

#### B. Preserve source and separate validation

- [x] Preserve the exact frontmatter envelope during every canvas body
save without parsing/stringifying YAML
- [x] Preserve comments, ordering, quoting, whitespace, BOM, newline
style, delimiters, and invalid but closed YAML
- [x] Keep the existing unsupported-node/property MDX recovery; do not
add an ad hoc partial parser
- [x] Parse body and frontmatter independently so either valid half
remains usable
- [x] Keep bounded Asset-query frontmatter extraction unchanged
- [x] Treat structurally malformed MDX as having no safe body tree

#### C. Share Asset state with the raw editor

- [x] Route canvas and complete-file changes through one ordered,
non-blocking per-Asset coordinator
- [x] Update canvas and clean raw editors immediately from shared
in-memory source
- [x] Reject stale dirty-editor saves, preserve unsaved text, and
require reload; never force-overwrite
- [x] Drain accepted writes after source replacement/disconnect and
report background failures globally
- [x] Dispose sessions only after they have no consumers and no queued
work

#### D. Connect, replace, and disconnect

- [x] Load and diagnose before connect; block only
unreadable/unauthorized/non-MDX Assets
- [x] Show **Abort / Connect** for non-empty blocks, including
diagnostics and the recoverable-content warning
- [x] Set `src` and delete persisted children through one runtime
mutation
- [x] Switch to a loaded source without waiting for the previous Asset's
network save
- [x] Remove disconnect confirmation and conversion
- [x] Reset `src` immediately, discard the derived tree, leave the block
empty, and preserve the Asset
- [x] Resetting a bound source leaves every repeated occurrence empty

#### E. Dynamic and shared sources

- [x] Keep one source/revision state per Asset and a separate derived
instances tree per occurrence
- [x] Resolve Templates independently per Content Block and preserve
Builder-only missing-template placeholders
- [x] Resolve bound `src` from the selected Collection occurrence
- [x] Make **Open** use the occurrence's resolved Asset; update the
filename and disable Open when unresolved
- [x] Add direct/bound tests including Collection item A opening `a.mdx`
and item B opening `b.mdx`

#### F. Diagnostics, MCP, and publication

- [x] Show an accessible warning icon beside Source with the first
diagnostic, line/column, and remaining count
- [x] Show precise YAML/MDX diagnostics in the Asset editor without
repetitive canvas/toast UI
- [x] Reuse parser/diagnostic/revision contracts in Builder, build,
publication, and MCP
- [x] Keep MCP frontmatter editing file-based and return structured
diagnostics for complete-source writes
- [x] Emit build warnings; published sites skip invalid/unsupported
content without diagnostic UI

#### G. Verification and documentation

- [x] Add fail/pass regressions for source preservation, independent
validation, stale raw saves, lifecycle, dynamic Open, shared Assets, and
cleanup
- [x] Run affected tests, typechecks, lint, formatting, package
boundaries, fixtures, and diff checks
- [x] Open Markdown and MDX editors at twice the default dialog size
with the right-side preview visible initially
- [x] Document canvas body editing, file-owned frontmatter, Content-mode
Asset access, and cloning an MDX Asset for a new post
- [x] Perform a final correctness, data-loss, DRY, simplification,
accessibility, and performance review
- [x] Keep this checklist synchronized with implementation status

### 20. Content-mode component properties

- [x] Show a connected Content Block Source read-only in Content mode
with an active **Open** action
- [x] Keep source switching, binding, reset, and disconnect available
only in Design mode
- [x] Expose HTML Embed code and preserve Code Text's code-editor
metadata on authored Text Content
- [x] Audit every registered component and expose authored payload while
keeping design, structure, and behavior controls in Design mode
- [x] Keep media dimensions and Code Text theme Design-only while
preserving adapter-owned intrinsic Image metadata during MDX round trips
- [x] Add exact component-permission and regression coverage
- [x] Update authoritative component documentation
- [x] Run affected full suites, typechecks, formatting, lint, package
boundaries, and generated contract/documentation checks


### 21. Designed document shell and Body outlet

- [x] Add one structural **Content Block Body** outlet and keep legacy
Content Blocks without it compatible
- [x] Materialize and persist only `document.body` children through the
Body outlet while preserving the surrounding designed shell and
Templates
- [x] Expose the connected document frontmatter through a Content Block
`document` parameter for shell text and property bindings
- [x] Write direct frontmatter-bound text and properties back through
the same ordered per-Asset session without blocking canvas mutations
- [x] Resolve document `$ref` dependencies and Asset references
consistently in Builder and published output
- [x] Keep referenced-document values read-only on canvas and direct
editors to the referenced file
- [x] Isolate repeated Collection occurrences while retaining
occurrence-specific source, frontmatter, shell, and Body content
- [x] Render the same designed shell, frontmatter values, and Body
substitution in generated/published output
- [x] Upgrade the local blog article layout to bind title, excerpt,
dates, author data, and images from frontmatter and render MDX through
Body
- [x] Add fail/pass regressions for nested Body discovery, lifecycle,
permissions, repeated values, document references, and generated shell
rendering
- [x] Update authoritative Content Block documentation
- [x] Run affected tests and typechecks, formatting, lint, package
boundaries, generated API/docs checks, and fixture builds


### 22. Final nested references and frontmatter editing

- [x] Keep direct frontmatter-bound text and property controls editable
in Content mode without exposing or replacing their bindings
- [x] Keep referenced-document values read-only and preserve legacy
instance-property name normalization
- [x] Render direct frontmatter text bindings through the plain-text
editing path
- [x] Resolve nested legacy and structured Asset references from
referenced Markdown/MDX frontmatter
- [x] Preserve missing Asset references, emit structured
`ASSET_NOT_FOUND` diagnostics, and keep them out of the document graph
- [x] Add fail/pass regressions for the real Settings control and
root/nested missing Asset-reference cases
- [x] Fix exact Content Block selection after the Body node appears in
Navigator
- [x] Run full affected suites/typechecks, all six MDX Content Block E2E
scenarios, deterministic fixture builds, generated checks, lint,
formatting, package boundaries, and diff checks

### Before merge

- [x] Resolve #6261: Nested Asset references in referenced Markdown
frontmatter work consistently in Assets queries, previews, publication,
and static export


### 23. Explicit writable expression bindings

- [x] Add an explicit `readwrite` mode to expression bindings; existing
expressions remain read-only by default
- [x] Accept `readwrite` only for direct static member paths and
centralize their parsing and validation
- [x] Require the expression root to provide a writable data source;
initially support the connected Content Block document frontmatter
- [x] Update Content Block permissions, canvas text editing, and
property controls to require a valid writable binding
- [x] Persist direct frontmatter edits through the existing ordered
per-Asset session
- [x] Create new direct frontmatter bindings with `readwrite` mode and
migrate existing compatible bindings
- [x] Preserve writable mode through schemas, serialization, loading,
copy/paste, transfer, and export
- [x] Add fail/pass regressions for valid paths, derived expressions,
unsupported sources, permissions, persistence, and compatibility
- [x] Review authoritative `docs/` content and update it if the
persisted binding contract affects user-facing documentation
- [x] Reject unsafe frontmatter paths and reject `readwrite` bindings
outside a connected Content Block document
- [x] Preserve expression binding mode through the public HTTP client
and document the MCP authoring contract
- [x] Limit frontmatter value write-back to Content mode so Design mode
retains normal binding replacement/reset behavior
- [x] Keep bindings inside Templates and across nested Content Block
ownership boundaries read-only
- [x] Upgrade compatible legacy bindings even when the Content Block is
disconnected during migration
- [x] Run affected tests, typechecks, formatting, lint, package
boundaries, fixture checks, and `git diff --check`

Verification: full SDK (567), Project Build (2,277), and Builder (2,695)
suites pass, along with affected Expression, Migration, Template, HTTP
Client, and MCP suites; typechecks, lint, formatting, package
boundaries, and diff checks pass. Generated contracts are refreshed.
Fixture synchronization remains blocked before generation because the
development API rejects this local CLI version as incompatible. The
checked-in bundles pass the current Protocol suite and every fixture
build with the current bundle version.
2026-08-29 20:15:20 +02:00
Oleg Isonen 7e7c14868a fix: Preserve read-only guides and publish targets (#6266)
## Summary

- Route explicit read-only `meta.guide` requests to focused discovery
tools and exclude mutations, side-effecting session tools, broad design
context, and visual verification.
- Require explicit task scope, specialized workflow, authored-fragment
routing, and design-system reuse instead of inferring behavior from
brief prose.
- Persist the requested staging or production target in new SaaS
deployment records.
- Return the same target from publish list and job status, with
backward-compatible inference for historical records.

## Todo

- [x] Reproduce the read-only custom-code audit misrouting from #6262.
- [x] Add failing regressions and implement strict read-only routing.
- [x] Remove behavior-affecting prose inference from CLI/MCP guide
routing.
- [x] Reproduce the single-domain production target mismatch from #6263.
- [x] Add create-to-list/status regressions and persist the explicit
target.
- [x] Cover historical deployment records that predate the stored
target.
- [x] Verify production create → list → get-job through the staging
deployment.
- [x] Regenerate and verify project fixtures against the changed bundle
contract.
- [x] Review documentation impact and update the generated MCP manual.
- [x] Review the complete diff for correctness, compatibility,
duplication, and unnecessary complexity.

## Verification

- Real staging publish for #6263 — passed; create, list, and get-job
consistently reported `production` for a one-domain publish. The
supplied project has no custom domains, so the custom-domain variant was
not available. The job remained `pending` during this check.
- Real local CLI/MCP verification for #6262 — passed; no mutation or
side-effecting tools leaked.
- `pnpm --filter @webstudio-is/protocol test -- src/fixtures.test.ts` —
17 passed
- `pnpm -r fixtures:build` — passed for all eight fixtures
- `pnpm --filter @webstudio-is/project-build test` — 2,134 passed
- `pnpm --filter webstudio test` — 1,037 passed; the one payload-size
regression was fixed and its targeted test passed
- `pnpm --filter @webstudio-is/domain test` — 24 passed
- `pnpm --filter @webstudio-is/sdk test` — 519 passed
- Builder API router test — 30 passed
- Relevant package typechecks — passed
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`
- Regression tests were demonstrated failing against the previous
behavior and passing with the fixes.
- Agent evaluations were not run because they require separate explicit
approval.

Closes #6262
Closes #6263
2026-08-29 18:54:05 +02:00
Oleg Isonen cbe4950cd0 fix: Use available package manager for previews (#6251)
## Summary

- Use the npm or pnpm launcher that started Webstudio for preview
dependency installation, builds, and server startup.
- Use pnpm-compatible isolated-install flags and report the selected
package-manager path on failure.
- Preserve the existing installed-dependency reuse path before
package-manager resolution.

## Technical choices

- Read the standard `npm_execpath` metadata already supplied by npm and
pnpm; users do not need to configure another environment variable.
- Run JavaScript launchers such as `pnpm.cjs` with the selected Node
runtime, supporting bundled Node distributions without adjacent npm.
- Keep the existing npx-to-npm mapping and fallback behavior unchanged.
- Distinguish numeric package-manager exit codes from process-launch
errors such as `ENOENT`.

## Todo

- [x] Reproduce the bundled Node + pnpm + missing adjacent npm failure.
- [x] Add fail-before/pass-after coverage for dependency installation,
production build, and preview startup.
- [x] Reuse the detected pnpm launcher.
- [x] Preserve installed-dependency reuse.
- [x] Include the selected launcher path in installation failures.
- [x] Report package-manager exits and process-launch errors accurately.
- [x] Review documentation impact and update the CLI manual plus
generated MCP documentation.
- [x] Review fixture impact; no fixture inputs or outputs are affected.

## Verification

- Focused install diagnostic regression suite — 28 tests passed.
- `pnpm --filter webstudio typecheck`
- `pnpm lint`
- `pnpm --filter webstudio test` — 1,029 of 1,030 tests passed; an
existing process-supervisor cleanup test hit a transient `ENOENT`, then
passed 4/4 when rerun in isolation.
- Generated documentation checks passed.
- Real pnpm JavaScript launcher invocation with isolated preview install
flags.
- Agent evaluations were not run because they require separate approval.

Closes #6250
2026-08-28 00:23:01 +02:00