mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-24 12:07:17 +09:00
docs
2548
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
2570bcdd69 |
fix: preserve cloned bindings and Content template design (#6533)
Cloning a Collection can leave its item bindings attached to the original Collection. Content-mode template insertion also drops fixed image attributes, rich-text editing can remove inline images, and YouTube previews retain the previous thumbnail after a URL edit. Reuse the Builder fragment-copy path for cloning, preserve exact fixed props from the same Content Block template, protect mixed image/text content from destructive editing, and refresh video previews. Direct component metadata lookup now includes hidden required parts without adding them to component lists. Unsupported Resource fields now return validation errors instead of unchanged success. Destructive confirmation failures retain their reason through the existing issue-report pipeline, and reports include the negotiated server API contract alongside the CLI contract. Contract versions still use the existing serialized-schema hash algorithm. ## Verification - [x] Reproduce each behavior fix and demonstrate regression tests fail before/pass after. - [x] Verify native Content template insertion, content editing, and reload through isolated local Builder E2E. - [x] Check copy permissions: reject arbitrary design values, reused/mismatched prop keys, changed clone structure, and copies from another Content Block. - [x] Remove the unused clone implementation and reuse the UI's copy/remapping logic. - [x] Review documentation impact; existing instructions remain accurate, no documentation update needed. - [x] Regenerate fixture builds from committed snapshots; generated fixture files remain unchanged. - [x] Complete generated API/docs checks, 2,492 project-build tests, 88 protocol tests, 99 focused CLI tests, package typechecks, lint, and formatting. - [x] Full Luna/low evaluation suite: 6/6 fixtures pass after preparing the Content runtime build. Existing baselines are incompatible with this model override; they were not updated. - [x] Independent Astra review: fixed and re-tested Global Root cloning and protected-prop overwrite findings; final review has no findings. - [x] Required CI passes: all 19 main-workflow jobs, visual regression, submodules, and PR lint. Closes #6531 Closes #6519 Closes #6521 Ref #6523 Ref #6524 Ref #6525 Ref #6526 Ref #6527 Ref #6528 Ref #6529 |
||
|
|
ff4a081f80 |
fix: export text pages in static builds (#6513)
Fixes #6440 ## Description Static exports currently omit Text pages such as `/robots.txt`, `/ads.txt`, and `/.well-known/security.txt`. This generates their routes, evaluates content through the existing page-data loader, and uses Vike's native `pageContext.content` to write the exact text at the configured file path. Internal route directories are separate from public URLs so dot-prefixed paths work. No generated writer script, text-page manifest, HTML wrapper, or post-build file rewrite is needed. ## Dependencies - Pin Vike to `0.4.267-commit-feffb0f`: native non-HTML output is published in this commit prerelease but is not yet in stable `0.4.267`. - Use Vite `7.1.12` and the compatible React plugin for SSG. Build through `vike build && vike prerender` so Vike uses its matching Vite dependency. - Update React Router's `vite-node` dependency to `3.2.4` for compatibility with the CLI's Vite 7 test environment. ## Validation - All 1,173 CLI tests pass locally across 72 files, including all 79 prebuild tests for normal HTML, dynamic SSG routes, and React Router builds. - Text export coverage checks exact output for `/robots.txt`, resource-backed `/.well-known/security.txt`, empty text, Unicode, and HTML-like content. - CLI and SSG fixture type checks, lint, and formatting pass locally. - SSG fixtures regenerated; the packaged SSG test passes locally. - All executed GitHub CI checks pass at `201fa971616bb607ca35ee24a970f107eddee754`, including all six browser shards. Shard 4 passed on retry after an MDX template rename timeout. Visual regression is skipped and preview deployment remains gated. - Corrected the existing animation navigation test for fork CI: wait for the rendered component with the public fallback and retain the animation initialization assertion when the private module is present. ## Manual verification 1. Export a static project with Text pages at `/robots.txt` and `/.well-known/security.txt`. 2. Run the generated project's build. 3. Confirm both files under `dist/client` contain exactly the configured text, including resource expressions. 4. Confirm normal pages still render HTML. - [ ] Original author tested the updated export and confirmed it works. - [ ] Preview tested (deployment approval remains gated). --------- Co-authored-by: Sviat Barbutsa <9897782+sviat-barbutsa@users.noreply.github.com> Co-authored-by: Oleg Isonen <oleg008@gmail.com> |
||
|
|
05ea26f6a6 |
fix: block publish when JSON prop value is missing (#6507)
## What failed A JSON prop can be saved without a `value`. The publish runner then rejects the build, but the Builder was starting the publish request before its best-effort diagnostics completed, so the user could miss the cause in the Builder. ## Fix - Add a pre-publish audit finding for JSON props without a value, linked to the affected element. - Block cloud and static publish requests when local publish audit errors are present, so the Builder shows the diagnostic before any build starts. - Keep the CLI diagnostic as a fallback for builds published through older clients or direct API flows. ## Verification - Project-build pre-publish audit tests pass (11 tests). - Project-build and Builder typechecks pass. - Oxlint passes for the changed files. |
||
|
|
3e310ddae3 |
fix: prevent canvas page switch reconciliation errors (#6494)
## Summary - Keep the Canvas iframe mounted on page switches to avoid the blank, slow document reload. - Key the page's React boundary and place authored content under a removable `display: contents` host node inside Canvas `<body>`. This gives page teardown a generic DOM boundary. - Use the merged sdk-components-animation fix from PR #36. The prior animation implementation removed React-owned text nodes and triggered `removeChild` when Canvas switched to an animated page. - Add an end-to-end regression using the actual animation components and retaining iframe identity. Keep the DOM-removal and preview navigation regressions. ## Validation - The animation regression reproduced the exact `removeChild` error before the animation fix and passed with the fix. - All checks passed on `f921c3011d`, including the submodule-ordering check, all six Builder E2E shards, Builder builds, app/package checks, typecheck, lint, and visual regression. - Live switching speed on the affected project still needs a manual check after deployment. |
||
|
|
54d720b185 |
fix: secure resource loader SSRF (#6497)
## Summary - Require authenticated project view permission on the resources-loader endpoint. - Restrict external resource fetches to HTTP(S) destinations with public IPs, pin DNS resolution to the validated socket address, and revalidate redirect targets. - Limit outbound connection origins and strip sensitive headers on cross-origin redirects. ## Validation - `oxfmt` and `oxlint` pass for the changed files. - Workspace TypeScript checking reports extensive unrelated missing workspace dependency/type errors; no diagnostics were reported for the changed files. - Tests were not run. |
||
|
|
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. |
||
|
|
c4684c141c |
fix: persist a valid JSON fallback when removing bindings (#6492)
## What changed Resetting a JSON binding stores the expression's evaluated value. If it evaluates to `undefined`, the serialized prop loses its `value` field and fails the build schema. This change saves `null` instead for a nullish fallback. ## Reproduction Using the SDK `prop` schema locally, a JSON prop with the `value` field omitted fails validation; adding `value: null` passes. The full test suite was not run. |
||
|
|
3d0f19ffe0 |
fix: complete and speed up publish validation (#6478)
## Summary Complete Validate for large projects with linked MDX content, using the same validation API in the dialog and MCP. Publishing continues independently of diagnostics. - Load MDX sources omitted from the embedded artifact and report storage failures with typed diagnostic reasons. - Reuse parsed MDX and template scope indexes; cache source reads in the shared asset loader. - Exclude conservative non-MDX query candidates while retaining explicit invalid references for diagnostics. - Give validation requests a 60-second server execution budget. - Support both server bundles in E2E tests and make OAuth logging initialization repeatable. - Raise the project bundle import limit from 20 to 32 MiB to reproduce the customer project. ## TODO / acceptance checklist - [x] Import and inspect the customer project locally and on staging. - [x] Reproduce missing embedded MDX sources and slow template materialization. - [x] Load omitted MDX sources and discover nested template dependencies. - [x] Report source-read failures using typed reasons rather than matching error messages. - [x] Reuse template scope indexes and remove duplicate source caching. - [x] Route dialog Validate through the same API as MCP, after flushing editor content. - [x] Preserve publishing in parallel with diagnostics, including failed or unfinished checks. - [x] Fix JSON collection files being treated as MDX query candidates. - [x] Preserve explicit invalid file references so they still produce diagnostics. - [x] Fix E2E routing across both generated server bundles and repeated OAuth logging initialization. - [x] Demonstrate the new review regressions fail before the fixes and pass after them. - [x] Run targeted tests, relevant typechecks, lint, formatting, package boundaries, and generated API checks. - [x] Run the full fixture pipeline and production builder build; no generated fixture changes. - [x] Build the exact staging project locally after the final review changes. - [x] Validate the exact staging project through MCP/CLI after the final deployment. - [x] Complete an actual staging publish after the final deployment. - [x] Address PR comments: simplify the stream mock, explain every diagnostic reason, and retain the existing diagnostic code contract. - [x] Review documentation impact; these fixes restore expected behavior and require no user instruction changes. - [x] Finish CI on the final commit, including all six Builder E2E shards. ## Verification Local checks passed: 179 project-build tests, 78 CLI prebuild tests, 8 asset publication tests, 42 server tests, and 9 publish UI/helper tests. The imported staging project also builds successfully. The new explicit-reference regression and repeated-initialization regression both failed before their fixes and passed afterward. A production build serves the login route and dispatches tRPC requests to its separate server bundle. Validation on staging project `b9d23e54-5dbf-45c4-b6d8-bc4cf842a468` completed in 25.3 seconds after deployment and 23.5 seconds after publishing: 92 documents included, 0 omitted, and 0 MDX errors. The [original staging publish job rerun](https://github.com/webstudio-is/webstudio-saas/actions/runs/36647770120/job/109819143695) succeeded end to end with the final artifact, including CLI/Remix builds, worker deployment, and database updates. [CI on commit `01a9d772dc`](https://github.com/webstudio-is/webstudio/actions/runs/36696154831) is green, including all six Builder E2E shards and Windows/Ubuntu fixture checks. The comment follow-ups simplify the publication test mock and document all diagnostic reasons. Production behavior is unchanged. The 8 publication tests, 6 schema tests, package typecheck, formatter, and lint pass. Disabling the source cache makes the simplified regression fail as expected. Final staging validation passed in 27.1 seconds with 92 documents and no omissions or MDX errors. One additional publish attempt hit a database statement timeout during project sync, before the build. A [retry with no code changes succeeded end to end](https://github.com/webstudio-is/webstudio-saas/actions/runs/36647770120/job/109826767589). CI is green on `01a9d772dc`. Ref #3550 |
||
|
|
e700a24210 |
fix: run publish checks in background (#6476)
## Summary - Start dynamic and static publish diagnostics in the background, without waiting for them before sending the publish request. - Error findings, failed or hanging checks, and failures while reporting them cannot block or delay publishing. - Keep the standalone Validate action's reporting behavior unchanged. - If diagnostics fail, the dialog explains that the publish request was sent without waiting for them. - The publish operation remains authoritative for authorization and publish-job failures. ## Verification - `pnpm --filter @webstudio-is/builder test -- app/builder/features/publish/publish-preflight.test.ts app/builder/features/publish/publish-error.test.ts` (8 tests passed) - `pnpm --filter @webstudio-is/builder typecheck` - `pnpm exec oxfmt` on changed files - `git diff --check` |
||
|
|
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. |
||
|
|
adf1fd3aaf |
fix: show publish status only for selected domain (#6473)
## Summary - Show the project-domain publish status only when the latest build targets that domain. - Keep the staging checkbox default tied to a staging publish. A pending publish to a custom domain no longer makes the staging domain appear to be publishing. ## Verification - `pnpm -r typecheck` passed locally. - GitHub Actions passed: checks, test matrix, builder end to end shards, visual regression, lint, and submodule check. |
||
|
|
e1e5bc152e |
feat: append response header rules for last-match precedence (#6460)
Response headers now use list order when more than one rule sets the same name on a path: the last matching rule wins. New or edited rules currently move to the top of the Builder list, which would make older rules override them. Append those rules instead and explain the precedence in the settings UI. Authentication list behavior is unchanged. Validation: SDK response-header tests and typecheck, CLI response-header tests, SDK build and declaration build passed. |
||
|
|
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. |
||
|
|
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. |
||
|
|
df7ed327ab |
refactor: clarify pathname rule matchers (#6446)
Authentication and custom response headers use the same route-rule syntax, but the shared matcher and validator had auth-specific names. Give those functions generic names in `wsauth` and update the Builder header UI and SDK response-header schema to call them directly. All basic-auth logic remains in `wsauth`; this change adds no package or re-export. The project-build page matcher is different: it uses `URLPattern` and returns decoded parameters. Rename it to `matchUrlPattern` and document the behavior difference. For example, `/docs/*` matches `/docs` for authentication and headers, but not for the page matcher. Tests cover both behaviors. This renames the two public `wsauth` route exports. Published workers pinned to the older package retain the old API; the SaaS worker can adopt the new names after this package is released. Validation: `wsauth` tests, including its packaged consumer test; SDK page and response-header tests; project-build route and redirect tests; SDK, `wsauth`, project-build, and Builder typechecks. |
||
|
|
be74d8d698 |
fix: Validate response headers and expose MCP rule edits (#6444)
Header rules previously accepted `null` values, which silently turned a rule into a header-removal instruction even though the settings UI only offers adding a string value or deleting a rule. Require a nonempty string throughout the SDK, Builder, CLI generator, and API contract. Expose configured rules to MCP through `list-response-headers`, `set-response-header`, and `delete-response-header`. Setting a rule adds or replaces the header for the same route and case-insensitive name; `/` and `/*` remain distinct. The Builder UI and MCP operations now share the same rule-key, edit, and Zod validation logic. The generated JSON Schema advertises the header-name pattern and nonempty value, while server-side Zod validation enforces route syntax, forbidden headers, duplicates, and total size. The generic `update-project-settings` operation still supports replacing the whole list. Validation: SDK, project-build runtime and MCP, CLI command and prebuild tests; SDK, Builder, project-build, CLI, and protocol typechecks; lint, package boundaries, generated API and docs checks. Local fixture sync against the development API is blocked by differing bundle versions; PR CI tests fixtures against its deployed Builder host. |
||
|
|
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.
|
||
|
|
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 |
||
|
|
30b4f994dc |
fix: restore publish countdown (#6443)
## Summary - Keep the publish countdown visible while the publish action is in progress. - Show the pending spinner after the 60-second countdown expires. The refactor in `804cd39bc` applied the design system's pending button state immediately. That state hides the button text, including the countdown label. Progress still disables the action throughout publishing, while the pending visual state now waits until the countdown ends. ## Verification - `git diff --check` |
||
|
|
cd64a6627e |
fix: Load unbound resource previews in the variable dialog (#6432)
New resources can fail to show data in the **New variable** dialog because a page-plan refresh cancels their preview request before it finishes. This change keeps explicitly requested previews alive until the dialog releases them. Changing inputs, changing resource type, or closing the dialog cancels obsolete work and prevents old data from showing. Unused page resources remain lazy; a preview still makes an on-demand request when **Load data** is clicked. The dialog stays usable while unrelated page resources are loading. Follow-up to #6030. ### Checklist - [x] Reproduce the cancellation with a regression test that fails before the fix. - [x] Keep dialog previews independent of page resource discovery, using the existing loader, cache, and request versions. - [x] Clear old previews on input changes and release requests on dialog close. - [x] Cover new Current date, Sitemap, HTTP, and existing unbound resources, plus repeated loads and stale responses. - [x] Keep tests for obsolete page-request cancellation and async resource dependencies passing. - [x] Test the unsaved dialog with an unrelated page request pending and a page-plan recalculation. - [x] Clear the old preview when switching resource types or starting an asynchronous body edit. - [x] Review documentation impact: no update needed; this restores the documented preview behavior. - [ ] Confirm GitHub CI passes. ### Verification - The dialog loading, type-switch, and body-edit regression tests failed before their fixes and passed afterward. - 82 focused tests and the settings-panel suite (588 tests) passed. - Builder typecheck and repository lint passed. - With preview retention temporarily disabled, all nine new loader regression cases failed as expected. |
||
|
|
9845517029 |
fix: hydrate published Markdown assets (#6422)
Migrated from #6421 to the short .staging branch name. |
||
|
|
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. |
||
|
|
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. |
||
|
|
cd1881bde6 |
fix: upload asset tickets serially (#6414)
## Summary - Reserve and complete each asset upload before requesting the next upload ticket. - Prevent concurrent identical uploads from triggering the server's in-progress deduplication error. - Keep the upload result mapping keyed by the folder-scoped upload key. ## Verification - `pnpm --filter @webstudio-is/builder exec vitest run app/builder/shared/assets/upload-assets.test.ts app/builder/shared/assets/directory-drop.test.ts app/builder/shared/assets/asset-upload.test.tsx` - 41 tests passed. |
||
|
|
0e30263787 |
fix: Fix multi-file asset drops and folder-scoped deduplication (#6413)
## Summary - Preserve all files when dropping multiple local files into Assets. - Scope client-side duplicate detection by folder. - Allow the same underlying file to have asset entries in different folders. ## Validation - Focused asset upload and directory-drop tests passed locally. - `git diff --check` passed. |
||
|
|
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 |
||
|
|
18d171212f |
feat: Resolve asset paths in HTML embeds (#6409)
## Description - Resolve root-relative script, stylesheet, image, and other asset attributes in HTML Embed markup. - Use the same asset-path map in Builder Canvas and generated published sites. - Keep SaaS asset proxy URLs deployment-relative instead of hard-coding the `p-….apps.webstudio.is` project origin. - Preserve canonical `/cgi/asset/` versus `/cgi/image/` routing and `?format=raw` behavior in Canvas. ## Regression coverage Added coverage for SaaS prebuild URLs, the real Builder asset-map-to-HTML-Embed integration, every supported asset attribute, HTML character references, and responsive-image `srcset` edge cases. ## Verification - [x] CLI prebuild suite: 69 passed - [x] HTML Embed suites: 63 passed - [x] Full Builder browser suite: 2,646 passed - [x] Asset URL map tests: 2 passed - [x] CLI, Builder, React SDK, and project-build typechecks - [x] Lint and package-boundary checks - [x] Previous PR CI: 29 successful, 1 skipped ## Code Review - [x] Self-reviewed - [x] Added regression and integration tests |
||
|
|
60c549c46f |
fix: preserve local styles and improve issue diagnostics (#6408)
Fixes #6386 ## Summary - Keep local `ws:style` declarations after `ws:tokens` so instance styles override token defaults. - Capture privacy-safe HTTP timing/status, exceptional session namespace states, preview freshness/version agreement, and validation paths in issue reports. - Derive reportable validation roots from the public operation catalog instead of maintaining a duplicate allowlist. - Keep captured failures current by clearing them after a successful tool call. ## Verification - Protocol issue-report tests: 3 passed - Project build MCP: 128 tests passed - CLI project session: 27 tests passed - Builder issue reporter: 5 tests passed - Relevant package typechecks passed - Generated API metadata is clean |
||
|
|
fc15ebf81a |
perf: avoid repeated MDX parsing in publish diagnostics (#6407)
## Description The cold `build.contentDatabasePublishDiagnostics` path was parsing the same reachable MDX documents while the publication plan converged, then parsing them a third time to produce omission diagnostics. Content-heavy projects can approach or exceed the 15-second serverless limit on that first request. This change: - creates a request-scoped MDX dependency resolver that reuses parse results by asset revision while the publication plan converges; - carries the final template omissions out of the existing publication pass, so the diagnostics endpoint does not walk the artifact again; - keeps the existing sequential asset validation/preparation/stability checks unchanged; - does not use module-level caching or depend on a warm serverless instance; - leaves the timeout error handling from #6406 unchanged. ## Performance evidence An isolated cold-path benchmark used the same MDX payload (2,000 sections) and compared the previous three independent dependency-resolution walks with the new request-scoped convergence path: | path | time | | --- | ---: | | before | 1,178 ms | | after | 404 ms | That is a 66% reduction in the measured MDX resolution phase. The tests also assert the underlying work reduction directly: an unchanged revision is parsed once across convergence passes, while a changed revision is parsed again. For a stable 214-document candidate set, this removes up to 428 of the previous 642 parse operations. ## Verification - `pnpm --filter @webstudio-is/project-build test` — 2,420 tests passed - `pnpm --filter @webstudio-is/builder exec vitest run --project core-server` — 336 tests passed, including server-side e2e runner tests - `pnpm --filter @webstudio-is/project-build typecheck` - `pnpm --filter @webstudio-is/builder typecheck` - `pnpm lint` - `pnpm check:package-boundaries` - `pnpm check:generated-api` ## Code Review - [x] made a self-review - [x] added comments for the request-scoped cache and revision safety - [x] added tests for parse reuse, revision invalidation, and the optimized endpoint integration path ## Before merging - [ ] tested on a deployment preview with a production-sized content project ## Additional content-read optimization Successive compilation plans now share already-fetched, revision-keyed content bytes within one callback-scoped preparation session. The byte store: - is created inside one asset-stability attempt and discarded when that attempt finishes; - is not stored on the repository, in a module, or outside the request; - remains bounded by the existing 2 MiB hydration limit; - refetches changed revisions and starts clean on stability retries. Tests verify that two overlapping plans read unchanged content once, a revision change forces another read, a new session reads again, and a stability retry creates a new session. A fully overlapping two-pass workload therefore performs up to 50% fewer object-storage content reads. Additional verification: - `pnpm --filter @webstudio-is/asset-uploader test` — 292 tests passed - `pnpm --filter @webstudio-is/asset-uploader typecheck` ## Remaining optimization assessment The percentages below estimate whole-request improvement and are workload-dependent; they are not additive. | optimization | estimated improvement | complexity | decision | | --- | ---: | ---: | --- | | Request-local content-byte reuse | 10–35% | 3/10 | Implemented | | Stop when the initial dependency plan is already a fixed point | 5–15% for static/known MDX; ~0% for dynamic discovery | 2/10 | Implemented | | Reuse a canonical source snapshot/materialized entries across different plans | 15–30% | 8/10 | Deferred: large semantic and invalidation surface | | Reuse the bundle's initial asset-data snapshot | 1–5% | 5/10 | Not worthwhile | | Add a diagnostics-specific bundle loader | 2–8% | 6/10 | Not worthwhile for this PR | | Parallelize or skip unrelated user/project metadata | 0–3% | 2/10 | Not meaningful | | Combine collection validation and reserved-asset inspection | 0–5% normally | 4/10 | Only meaningful for projects with many collections | The fixed-point fast path is covered by a test asserting one artifact preparation and one dependency-resolution call when the original plan is already stable. |
||
|
|
0d448313ab |
fix: harden MCP mutations and validation (#6402)
## Summary Fixes the reproducible MCP regressions reported today: - validate font upload metadata locally instead of surfacing an HTTP 500 - replace only the addressed child when `insertIndex` is supplied - support cross-parent duplication - bind data-variable assignments in actions - reject string-valued `style` bindings - include descriptions for every advertised MCP tool - preserve resource data-source exposure on full updates - render mixed expression/element children in the Builder canvas - preserve explicit outer Collection references in nested scopes - reject unsupported `update-page-settings` fields instead of dropping them - reject unavailable asset-query variables and log server error details Closes #6390 Closes #6391 Closes #6392 Closes #6393 Closes #6394 Closes #6395 Closes #6398 Closes #6399 Closes #6401 Closes #6404 Closes #6405 ## Verification - 484 project-build tests - 62 CLI tests - 35 Builder browser tests - 25 SDK tests - project-build, Builder, CLI, and SDK type checks - root lint and generated runtime contracts |
||
|
|
aa1587c100 |
fix: Clarify publish timeout errors (#6406)
## Summary - detect pre-publish HTTP timeouts from the response metadata already attached to `TRPCClientError` - show a clear publish-specific message instead of the JSON parser error - cover both HTTP 504 and Vercel timeout responses ## Why When `build.contentDatabasePublishDiagnostics` exceeds the serverless function duration, the proxy returns HTML or plain text. tRPC's JSON parser fails, but the resulting client error still contains the original HTTP response. The publish flow now uses that response to report: > Pre-publish checks timed out. Publishing was not started. Please try again. This PR intentionally does not change the shared tRPC transport or claim to solve the underlying diagnostics performance problem. ## Verification - `pnpm --filter @webstudio-is/builder exec vitest run --project core-browser-builder-features app/builder/features/publish/publish-error.test.ts` - `pnpm --filter @webstudio-is/builder typecheck` - `pnpm exec oxlint --deny-warnings <changed files>` - `pnpm exec oxfmt --check <changed files>` |
||
|
|
b7a11e4bc2 |
feat: Delay canvas scripts until resources render (#6380)
HTML Embed scripts in canvas and preview now wait until the complete
resource queue settles. Their markup remains rendered while loading;
only executable scripts are delayed, so scripts can read the committed
resource-backed DOM without requiring a custom event.
## Implementation
- Add an internal `$resourcesState` atom with `pending` and `settled`
states, driven by the complete resource queue.
- Synchronize the atom from the Builder to the canvas iframe.
- At the Builder component-render boundary, pass only HTML Embed the
private prop `$ws$executeScripts={isSafeMode !== true && resourcesState
=== "settled"}`.
- In the SDK HTML Embed component, default `$ws$executeScripts` to
`true`, keep markup inert while it is false, and execute scripts after
React commits the transition to true.
- Treat failed requests as settled so scripts cannot wait forever.
- Keep the private prop out of generated component arguments and
metadata through the existing `$`-prefixed prop convention.
- Keep published rendering unchanged because published sites do not pass
the private Builder prop.
- Keep the existing standard `DOMContentLoaded` / `load` compatibility
and sequential script queue.
- Remove the canvas wrapper and component-registry override.
## Todo
- [x] Track collective resource readiness in the Builder.
- [x] Synchronize readiness into the canvas.
- [x] Delay HTML Embed scripts in both canvas and preview.
- [x] Combine safe mode and resource readiness into one private
execution prop.
- [x] Preserve rendered HTML while scripts are delayed.
- [x] Preserve pages with no resources and failed-resource behavior.
- [x] Keep the private prop out of user-facing component metadata.
- [x] Add resource-state, sync, Builder-prop, and browser regression
tests.
- [x] Review documentation impact; no user-facing documentation changes
are needed.
- [x] Review fixture impact; no fixture inputs or generated outputs are
affected.
## Verification
- SDK HTML Embed browser tests — 44 passed
- Builder private-prop tests — 15 passed
- Resource tests — 28 passed
- Sync tests — 14 passed
- React SDK, SDK components, and Builder typechecks — passed
- Focused Oxlint and formatting — passed
Closes #6379
|
||
|
|
9963c95ef8 |
fix: Sync OAuth profile fields on login (#6376)
Closes #6375 ## Summary - Refresh username, avatar, and provider fields when an existing user logs in through OAuth. - Avoid unnecessary database writes when profile fields are unchanged. - Preserve existing development-login behavior. - Add regression coverage for changed GitHub profile data. ## Testing - `pnpm --filter @webstudio-is/builder exec vitest run --project core-server app/shared/db/user.server.test.ts` - `pnpm --filter @webstudio-is/builder typecheck` |
||
|
|
2aec79774e |
fix: focus spacing input when popover opens (#6374)
## Description Restore automatic focus to the CSS value input when the spacing popover opens. The property label became focusable after its migration to `ResettableLabel`, making it the first tabbable element in the popover and causing Radix to focus the label. Marking the intended value input with `autoFocus` makes React focus it as the popover content mounts; Radix then detects that focus is already inside its focus scope and preserves it. ## Steps for reproduction 1. Select an element. 2. Open the Style panel spacing section. 3. Click any margin or padding value. 4. Verify focus is placed in the CSS value input. ## Verification - [x] Builder typecheck - [x] Prettier check - [x] Oxlint - [x] `git diff --check` ## Code Review - [ ] conceptual review - [ ] detailed review - [ ] test on preview |
||
|
|
649ee3669d |
fix: Batch asset queries during project import (#6368)
## Problem Project import sent every asset filename in one database request. Large projects produced an oversized URL and failed before assets could upload. Closes #6367 ## Changes - [x] Reuse the existing encoded filename chunking rule for imports - [x] Batch imported file lookup and restore requests - [x] Add regression coverage for long filename lists - [x] Review documentation impact: no user-facing documentation changes are needed ## Verification - [x] Builder import tests: 21 passed - [x] Asset uploader tests: 50 passed - [x] Builder and asset-uploader typechecks - [x] Lint, formatting, and diff checks - [x] Real CLI import passed the original metadata lookup and began uploading 380 assets - [ ] Agent evaluations were not run; they require separate approval The real import later reached the target project's separate 350-asset limit. That limit is outside this fix. |
||
|
|
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 |
||
|
|
39c290bfa7 |
fix: resolve MCP-reported runtime and component issues (#6362)
## Summary - harden MCP runtime behavior around asset uploads and resources, HTTP responses, component and Content Block application, data reads, and Chromium screenshots - reject shared Slots inside Content Block templates and avoid unsafe unused-asset findings when Content Blocks can hide MDX dependencies - expand named grid-area values according to CSS shorthand fallback rules - add a consent-safe YouTube preconnect control and only warm origins that are actually used - add focused and end-to-end regression coverage for the reported paths ## Verification - project-build: 2,419 tests passed - css-data: 4,623 tests passed, 1 skipped - sdk-components-react: 134 tests passed, 1 skipped - high-impact CLI fixture: 7 tests passed - Builder asset REST service: 22 tests passed Closes #6336 Closes #6339 Closes #6353 Closes #6354 Closes #6355 Closes #6356 Closes #6357 Closes #6358 Closes #6359 |
||
|
|
eb61139e5a |
feat: Scale publishing allowance with workspace seats (#6352)
Builder already counts production builds for the project owner, including publishes created through MCP and CLI. This change scales the allowance used by Builder with the owner's paid seat capacity, so five seats at 100 publishes each give 500 publishes per day. Free stays at 10. Closes #6351 ## Implementation The existing `userPublishCount` endpoint still reads the owner-wide `user_publish_count` view. For a project, it now returns the project owner's base allowance multiplied by the owner, included member seats, and purchased extra seats. Builder uses that returned allowance in its existing publish UI. The count remains owner-wide. MCP and CLI publishes contribute to it because they create production build records, but their publish endpoints do not enforce this Builder-side limit. No database, server publish-path, dependency, fixture, or generated-file changes are included. ## Checklist - [x] Calculate the paid allowance from the owner, included seats, and purchased extra seats. - [x] Keep Free at 10 publishes per day. - [x] Reuse the existing owner-wide publish count. - [x] Use the calculated allowance in Builder. - [x] Review documentation impact; no Builder documentation changes are needed for this small allowance update. - [ ] Update the pricing page to explain the publishing allowance and how seats increase it. - [x] Review fixture impact; fixture inputs and outputs are unchanged. - [x] Review the final diff for unnecessary changes. ## Verification - The project router tests pass, including the five-seat 500-publish allowance. - The new assertion fails against the previous implementation and passes with this change. - Builder browser tests passed. - Project and Builder typechecks passed. - Formatting, lint, and package-boundary checks passed. - All GitHub Actions workflows passed. - Agent evaluations do not cover this flow and were not run. |
||
|
|
13952a7d34 |
fix: Preserve Global root tokens when importing templates (#6348)
Inserting the CMS Sitemap marketplace page could detach existing Global root tokens and break styles across existing pages. Preserve the root's existing style-source selections in their original order, then append mapped incoming sources without duplicates. Incoming local declarations continue to merge into the existing root local source. The MDX save fix is now included in main through `commitAssetContentUpdate`. This PR retains an E2E assertion that saved MDX revisions are not persisted again through `build.patch`. Closes #6347 Verification and delivery: - [x] Add four regression cases covering empty, local-only, token-only, and mixed incoming root selections, including repeated imports, token remapping, and local declaration merging. All four failed before the fix and pass after it. - [x] Rebase onto main and rerun style-copy and page-copy tests: 55 passed. Formatting and diff checks passed. - [x] Verify the cloned project locally: reproduce the broken Home canvas before the fix and restore it with Undo. After the fix, insert Sitemap and verify Home retains its logo sizing, typography, spacing, rounded corners, and both Global root tokens after reload. - [x] Verify the MDX E2E assertion fails with duplicate persistence and passes with the fix. Before rebase, all 13 shard 4 tests passed locally with four workers, along with relevant typecheck and lint checks. - [x] Resolve the rebase conflict using main's shared MDX implementation and remove the duplicate helper from this branch. - [x] Review documentation impact against marketplace, CSS-variable, and Content Block documentation: no documentation changes required. - [x] Review fixture impact: no fixture inputs, serialization formats, or generated contracts changed. No migrations are required. This prevents future imports from dropping root selections; it does not reconstruct selections already lost in previously saved projects. |
||
|
|
e69a3d2f8e |
feat: Create structured MDX entries from collection folders (#6317)
## Paragraph editing follow-up — |
||
|
|
e33ce76489 |
fix: Keep image picker width stable and accept quick clicks (#6344)
Choosing an image now works with a quick thumbnail click, and selecting an asset keeps the Source picker at its intended width, including long names and nested folder paths. The dialog's content-based width was overriding the floating panel's width. Focusing a thumbnail widened the panel, triggering resize handling that briefly disabled pointer events before mouse release. Pass the floating panel styles through the dialog's `css` override so the intended width wins. Explicit dimensions and drag/resize handlers remain as implemented. Closes #6343 - [x] Reproduce both failures in Chromium using the actual Source picker. - [x] Fix floating panel style precedence. - [x] Add three regressions for root and nested asset selection widths and an 80 ms thumbnail click. All three fail without the fix and pass with it. - [x] Run related Builder tests (50 passed), the design-system suite (228 passed), both package typechecks, repository lint, and formatting. - [x] Review documentation impact: the Image Source instructions already describe the restored behavior; no update needed. - [x] Verify no fixture inputs or generated outputs are affected. Validation used headless Chromium component tests. Full canvas E2E and Storybook screenshot checks were not run. |
||
|
|
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
|
||
|
|
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. |
||
|
|
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. |
||
|
|
d292502037 |
fix: Paste in Firefox navigator (#6329)
## Summary - Read every clipboard mime type before the first await in both paste handlers. - Call the paste handler registered by the active mode from `emitPaste` instead of dispatching a synthetic clipboard event. - Add regression coverage for both paths: a clipboard event that disconnects after dispatch, and a Firefox-shaped `ClipboardEvent`. ## Root cause Two independent Firefox behaviors, one per entry point. Keyboard paste: the plugin loop re-read `event.clipboardData` on every iteration. Firefox disconnects a paste event's `DataTransfer` as soon as the handler yields, so once `pageText` — first in the list, and async — awaited, every later plugin read an empty string and `instanceText` never received the payload. Chrome keeps the data readable across microtasks, which is why this only surfaced in Firefox. Introduced in #5798, which put `pageText` at the head of the plugin list. Context menu paste: `emitPaste` dispatched `new ClipboardEvent("paste", { clipboardData })`. Gecko's `ClipboardEventInit` has no `clipboardData` member, so the event arrived with a non-null but empty `clipboardData`. `validateClipboardEvent` only rejects a null one, so the paste silently did nothing — no toast, no error. Measured in Firefox 134 against Chrome and Edge 152: `getData` after one microtask returns the payload in Chromium and an empty string in Firefox; a synthetic event's `getData` likewise returns the payload in Chromium and an empty string in Firefox. No behavior change in Chromium. Every plugin returns `pasteIgnored` before its first await, so reading all mime types up front yields the same input the loop received before. ## Verification - `pnpm --filter @webstudio-is/builder exec vitest run app/shared/copy-paste app/builder/shared/asset-manager app/shared/commands-emitter.test.ts` — 335 tests passed. - `pnpm --filter @webstudio-is/builder typecheck` — passed. - `pnpm exec oxlint --deny-warnings apps/builder/app/shared/copy-paste/` — passed. - `pnpm exec oxfmt --check apps/builder/app/shared/copy-paste/copy-paste.ts apps/builder/app/shared/copy-paste/copy-paste.test.ts` — passed. - Reverted each fix in turn against the new tests: 13 failures for the keyboard path, 2 for the context menu, all passing again once restored. - e2e runs Chromium only, and the previous test `DataTransfer` never disconnected, so neither path was covered before. Closes #6288 --------- Co-authored-by: Anmol <anmol@fathershops.com> Co-authored-by: Oleg Isonen <oleg008@gmail.com> |
||
|
|
e33a269802 |
fix: show unsupported browser warning in a dialog (#6332)
The unsupported-browser warning now uses the Builder’s regular modal dialog. Users can acknowledge it and continue, while the small-window state remains a blocking alert. Closes #6331 ## Implementation - Render the existing browser guidance and links inside the design-system `Dialog`. - Set the dialog to modal and keep close, Escape, outside-click, and **Continue** dismissal behavior. - Keep unsupported-browser detection, preview-mode suppression, and Builder-ready timing unchanged. - Keep the small-window alert blocking and remove its now-unused dismissible-alert path. - Add a focused Storybook state and browser regression test. - Increase Main and Visual Regression workflow job timeouts from five to seven minutes after otherwise healthy jobs were cancelled during slow dependency setup. - Run failed E2E diagnostics and service teardown through the backend shell script so cleanup does not depend on pnpm remaining on `PATH` after cancellation. - Route `safe-to-deploy` label events through one reusable gate that starts both Main and Vercel workflows. - Remove revision-specific deployment and visual approval labels in one synchronization-only cleanup job. - Reduce expected skipped jobs from 6 to 1 on a normal branch update and from 17 to 3 on a visual approval label event. No migrations or compatibility changes are required. ## Todo - [x] Replace the unsupported-browser alert with a modal dialog. - [x] Preserve the small-window blocking alert. - [x] Add regression coverage. - [x] Give CI enough time for slower dependency setup. - [x] Keep E2E cleanup available after runner cancellation. - [x] Simplify label-triggered Main and Vercel workflows. - [x] Consolidate revision-specific approval cleanup. - [x] Review documentation impact: no product documentation changes are needed for this presentation-only warning update. - [x] Review fixture impact: no fixture inputs or generated fixture outputs are affected. ## Verification - [x] Focused browser regression test - [x] Builder typecheck - [x] Oxlint on changed files - [x] Oxfmt and `git diff --check` - [x] Workflow YAML parsing and backend shell syntax checks - [x] Workflow trigger, nesting, permissions, and reusable-job contract assertions - [x] Prettier checks for changed workflows - [ ] Storybook screenshot review was not run locally because explicit visual-test approval was not available. ## Known limitations - The visual state was not screenshot-tested locally. - The reduced `pull_request_target` fan-out takes effect after these workflow files reach the default branch, because GitHub loads those workflow definitions from that branch. |
||
|
|
b832a8e808 |
refactor: Centralize Builder ID generation (#6323)
## Outcome Builder-owned code now routes standard ID generation through `@webstudio-is/sdk/createId` while preserving existing NanoID and UUID formats. Closes #6322 ## Technical choices - Use `createId("nano")` for Builder records, assets, transaction IDs, and other existing NanoID-backed values. - Use `createId()` for UUID-backed database records and browser-side client IDs. - Keep `BuilderRuntimeContext.createId` as an injectable dependency. Runtime operations, copy/paste, and deterministic tests rely on that injection. - Keep Node's `randomUUID` in CLI paths that explicitly support running without the Web Crypto global. - Keep specialized generators for custom-alphabet project domains and actual hash generation. - Remove direct `nanoid` dependencies from packages where no specialized use remains. - Do not migrate existing data or change database schemas. ## Todo - [x] Migrate Builder app ID creation. - [x] Migrate project runtime and template defaults. - [x] Migrate asset, project, domain, and authorization-token IDs. - [x] Preserve runtime ID injection. - [x] Review remaining direct generators as explicit exceptions. - [x] Add NanoID-format regression coverage for the default runtime context. - [x] Review documentation impact: no user-facing documentation changes are needed. - [x] Review fixture impact: no fixture input or generated output format changes are needed. - [x] Run targeted tests, typechecks, lint, and package-boundary checks. ## Verification - Targeted Builder tests: 153 passed after preserving runtime-context injection. - Project-build runtime tests: 270 passed. - Asset uploader tests: 61 passed. - Authorization-token tests: 17 passed. - Project tests: 52 passed. - Domain tests: 18 passed. - CLI tests: 32 passed. - Typechecks passed for Builder, project-build, asset-uploader, authorization-token, project, and domain. - `pnpm lint` passed. - `pnpm check:package-boundaries` passed. - The runtime-context format test was demonstrated to fail with a UUID default and pass with the NanoID default. - Agent evaluations were not run because they require separate explicit approval. |
||
|
|
fc9fa39fbc |
fix: Reduce style source padding (#6315)
## Result Style source controls now use spacing level 1 for their inner padding instead of spacing level 2. ## Implementation - [x] Change only the inner padding of `StyleSourceControl`. - [x] Keep interactions and accessible behavior unchanged. - [x] Review documentation impact: no documentation changes are needed for this visual spacing correction. - [x] Verify formatting, lint, and Builder types. ## Verification - `pnpm exec oxfmt --check apps/builder/app/builder/features/style-panel/style-source/style-source-control.tsx` - `pnpm exec oxlint --deny-warnings apps/builder/app/builder/features/style-panel/style-source/style-source-control.tsx` - `pnpm --filter=@webstudio-is/builder typecheck` No fixture inputs or generated outputs are affected. No known limitations. Closes #6314 |
||
|
|
d4b1994a6a |
fix: Improve editor selection and project tag contrast (#6302)
Closes #6301 ## Summary - Restore Dashboard project tags to the pre-color-refactor treatment: fixed white text over a 30% black scrim in both application themes. - Use one high-contrast text-selection treatment across native inputs, textareas, contenteditable fields, and every shared CodeMirror editor. - Preserve the existing subtle selection treatment for Navigator, Project Settings, command menus, Dashboard navigation, and other selected controls. - Add direct Storybook coverage for the CSS fragment editor. - Simplify chromatic color derivation from 571 to 329 lines without changing computed colors. ## Technical choices - Keep project tags independent from application-theme polarity because they overlay arbitrary thumbnail artwork. - Keep the existing `selectionBackground` control-state export unchanged at its original 12% light / 20% dark accent blend. - Define separate `--background-text-selection` and `--foreground-on-text-selection` tokens exclusively for selected text. - Use the bounded accent in light mode and a luminance-lifted accent in dark mode so selected text remains distinct even with extreme custom theme colors. - Style CodeMirror through its existing selection layer and `::selection` CSS hooks; no selection tracking or editor-state logic is added. - Leave canvas rich-text selection independent: authored canvas documents do not load the Builder UI theme stylesheet. - Replace five duplicated XYZ clamp formulas with the exact upper-luminance cap they reduce to. The removed lower-bound and white-mixing branches were unreachable because the minimum was fixed at zero. ## Todo - [x] Restore the original project-tag scrim and foreground in both themes. - [x] Inventory native, CodeMirror, and canvas rich-text selection paths. - [x] Unify native and shared CodeMirror text-selection colors. - [x] Keep text selection independent from selected control and navigation states. - [x] Add the missing CSS fragment editor story. - [x] Add light- and dark-mode browser regressions and confirm they fail before the fix. - [x] Enforce 4.5:1 selected-text contrast and 3:1 highlight-to-surface contrast across supported theme inputs. - [x] Remove duplicated and unreachable chromatic color derivation while preserving outputs. - [x] Review documentation impact; the design-system color-token reference is updated, with no user-facing product documentation change required. - [x] Verify fixture impact; no fixture inputs or generated outputs are affected. ## Verification - `pnpm --filter @webstudio-is/design-system test` — 227 tests passed, including the control/text separation regression, Navigator tree interaction, default-palette comparisons, and the full supported-theme contrast sweep - `pnpm --filter @webstudio-is/design-system typecheck` - `pnpm --filter @webstudio-is/builder exec vitest run app/shared/code-editor-base.browser.test.tsx --project app-browser-shared` - `pnpm --filter @webstudio-is/builder exec vitest run app/dashboard/projects/project-card.browser.test.tsx --project app-browser-canvas-dashboard` - `pnpm --filter @webstudio-is/builder typecheck` - Targeted `oxlint`, formatting, and `git diff --check` - Live dark-mode Dashboard verification of the restored subtle navigation selection - Storybook visual checks for the full color system and input/CodeMirror selection in light and dark modes |