mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-24 20:17:07 +09:00
main
4186
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd99d27e5e |
fix: Stop returning unqueryable unpublish jobs (#6588)
Unpublish finishes in the request, but it returned a job ID that `get-publish-job` could never find. It now returns the domains it unpublished. A missing publish job also stays a normal not-found error instead of suggesting a CLI update. Closes #6578 ## Changes - Keep filename-based E2E shard discovery. Rebalance the files across ten shards using measured CI times; each shard keeps its own test environment and the same 12-minute safety timeout. - Remove the fabricated unpublish job ID. `get-publish-job` remains for publish jobs. - Remove publish-only flags from the unpublish CLI and clarify CLI/MCP guidance. - Keep legacy optional server inputs for compatibility. The API input contract does not change; clients that read the old `jobId` result need to use `unpublishedDomains` instead. - No database migration. Existing domain-removal behavior is unchanged, including retention of other published domains. ## Checklist - [x] Reproduced both reported failures with tests before the fix. - [x] Reviewed the change for compatibility, redundant code, and retained-domain behavior. - [x] Updated the authoritative MCP docs and regenerated the CLI/MCP docs. - [x] Ran focused Builder, CLI, domain, and build tests; workspace typecheck, lint, package-boundary, generated API, and generated-doc checks. - [x] Restore filename-based shard discovery and rebalance the files across ten shards. - [x] Verify every Chromium workflow test runs in exactly one shard and update the E2E contributor docs. All 141 tests are assigned once across the ten shards. - [x] Verify parallel-safe long E2E files and the two-submission legacy form test in CI. - [x] Let independent slow E2E files run their tests concurrently; all ten shard jobs passed. - [ ] Confirm all E2E jobs finish in under five minutes. Eight did; shard 1 took 5:10 and shard 4 took 6:32. Move the long Content-mode page and shared-project bridge files to separate shards, then remeasure. - [x] Required CI checks pass. No live unpublish was run because it would change a published site. Agent evaluations were not run; they require separate approval. |
||
|
|
75d710e759 |
fix: Let Editor links patch Content Block text and props (#6575)
Editor links with API access can edit text and editable props inside Content Blocks through raw CLI/MCP patches. Builder and Viewer permissions stay unchanged. UI and API requests use the same Content-mode policy. The API keeps its narrower text-and-props scope; page, asset, style, expression, and structure changes remain blocked for raw Editor patches. ### Implementation - Use the persistence patch engine for instance, prop, and style state changes. Remove the separate instance patch interpreter and record writes from permission validation. - Check prop changes against the resulting state. Image dimensions work even when their patches appear before the source patch. - Move Image source, dimensions, and alt updates into the shared runtime. Video source changes preserve its existing dimensions. The UI no longer sends separate follow-up writes. Explicit overrides and expression bindings are preserved. - Separate capability discovery from transaction validation and move the server permission adapter out of the router. - Reject missing replacement targets and protected or renamed prop-ID reuse. Keep template-copy protections. - This refactor removes 19 non-test lines. It removes eager whole-instance cloning, but the server still loads the project and validates its content tree. Empty transactions skip that validation. Closes #6573 ### Checklist - [x] Reproduce the Editor-token denial and add regression coverage - [x] Keep Editor raw patches limited to text and editable props - [x] Use the existing patch engine and test missing targets, immutable input, and permission boundaries - [x] Share media updates between UI and MCP; test atomic updates, patch ordering, overrides, and bindings - [x] Separate capability discovery, permission validation, and API orchestration - [x] Verify new regression tests fail with the relevant behavior disabled, then pass after restoration - [x] Run runtime tests, API/sync tests, typechecks, lint, formatter, package-boundary, and generated-API checks - [x] Run Content-mode E2E checks for templates, assets, page creation, collaboration, and save/reload - [x] Add a CLI MCP Editor-token save/reload E2E, including protected template rejection - [x] Return a structured forbidden error for rejected Editor MCP content changes - [x] Run the complete Luna/low evaluation suite; record failures below - [x] Review documentation and fixture impact: no new API inputs, schema changes, migrations, or fixture generation changes; existing guidance remains applicable - [x] Correct the project-package permission fixture to include its owning instance and run all package tests - [x] Cover repeated media source updates and bound namespace batches in Editor requests - [x] Preserve stale retry handling before Editor content validation, with API regression coverage - [x] Preserve expression-bound media metadata during source replacement and test saved Content-mode output - [x] Reuse batch merging so split prop payloads validate against their final state; test split and combined forms - [x] Push the refactor and verify required CI completes - [ ] Deferred: reproduce the separate Builder UI report on a session-free Editor link - [ ] Deferred: confirm image-description editing expectations separately ### Verification - Full project-build suite: 2,597 tests passed. Focused permission/prop run: 121 passed. - All package tests (`pnpm test:packages`) passed after correcting the project-package fixture. All 1,297 CLI tests passed. - Builder API and sync authorization: 54 passed; settings-panel tests: 11 passed. - Content-mode E2E: 9 passed, including setup, plus the earlier focused Image replacement run. - Builder/project-build typechecks, lint, formatting, package boundaries, and generated-API checks passed. The CLI content-runtime bundle test passed; no generated fixture output changed. - Earlier full Luna/low evaluations: **7/9 passed**. `managed-form-runtime-v1` failed `runtimeFormAction`; `markdown-blog-v1` failed `blogRouteEvidence`. These checks also failed before this refactor. MDX editing and Markdown-reference discovery passed. Baselines for this model/effort were incompatible or missing and were not updated. This suite is not fully green. - Latest full Luna/low rerun after review fixes: **6/9 passed**. Form runtime, blog authoring, and MDX editing failed; the MDX fixture passed in the earlier run. Baselines were not updated. The evaluation suite remains incomplete. - Retry follow-up: all 59 API/router tests and 28 persistence patch tests passed, along with Builder typecheck and lint. Older retries use existing persistence handling; current and future versions remain permission-checked. - Bound-media follow-up: Image and Video source replacement preserves expression-bound width/alt and still passes shared Content-mode validation. Full runtime tests, typecheck, and lint passed after this fix. The agent evaluation results above precede the final retry and binding guards; those guards have dedicated regression coverage. - Split-payload follow-up: reuse the existing batch merge helper before permission validation. Combined and split media patches pass the same final-state checks. Full runtime tests, 59 API tests, typechecks, and lint passed. Agent evaluations were not repeated after this follow-up. - CI passed on `1242ba8344`: all package, Builder, and CLI test jobs; all six Builder E2E shards; visual regression; typecheck/lint; generated-file checks; packaged SSG; size; migrations; and submodule checks. Development deployment checks passed. All review threads are resolved. ### Final review follow-up (2fbb30ffe6; CI passed) - [x] Reject future base versions before saving; test an otherwise allowed Editor patch - [x] Preserve sequential patch behavior and test authorization/persistence parity - [x] Run focused tests, typechecks, lint, and review documentation/fixture impact - [x] Commit and push these follow-up fixes, then verify CI Local follow-up verification: 2,600 project-build tests, 96 project tests, and 60 API tests passed. Builder/project-build typechecks, changed-file lint and formatting, package boundaries, and diff checks passed. Regression tests failed without the fixes and passed with them. The shared patch engine uses one Immer draft and supports namespace replacement without mutating input. Net production change: 6 lines. No documentation, fixture inputs/outputs, schemas, or generated contracts changed. Browser E2E and agent evaluations were not rerun for this follow-up; the race and patch-order cases are covered by deterministic tests. CI passed on `2fbb30ffe6`: all six Builder E2E shards, package/CLI/Builder tests, typecheck/lint/generated checks, visual regression, migrations, SSG build, size, and submodule checks. No new blocking findings in the follow-up review. Agent evaluations were not rerun; earlier limitations remain recorded above. ### Asset review follow-up - [x] Reproduce and block asset-valued design-only props, retaining matching Image metadata - [x] Stop generating image metadata for Video source updates - [x] Verify regression tests, documentation/fixture impact, and CI; reply to both review threads Asset follow-up `2ea28028c0`: shared metadata eligibility now checks asset values and their matching Image source, including MDX materialization. Video source edits do not generate Image-only metadata. New and updated regression tests failed without the fixes and pass with them. All 2,604 runtime tests, 60 API tests, 30 persistence tests, both typechecks, lint, formatting, and package boundaries passed. No documentation, schemas, or fixture data changes are needed. Agent evaluations were not rerun. Both review threads have replies and are resolved. CI passed on `2ea28028c0`, including all six E2E shards, visual regression, package/CLI/Builder tests, typecheck/lint/generated checks, migrations, SSG build, size, and submodules. - [x] Reproduce repeated source updates without a saved source, coalesce before ID assignment, and verify regression tests/CI Final source follow-up `16c1c5f68e`: repeated instance/property updates are coalesced before assigning IDs, replacing the later source-only check. Both new Image/Video regression cases failed before the fix and pass now. All 2,606 runtime tests, typecheck, and lint passed. CI passed on this commit, including all six E2E shards and visual regression. No documentation or fixture-data changes were needed; evaluations were not rerun. ### Coverage audit - [x] Audit changed behavior against assertions, including UI/API and persistence boundaries - [x] Add test assertions for identified coverage gaps; automatic CI passed - [x] Review documentation and fixture impact: no product documentation change; one new SVG used only by browser E2E Per request, no local tests or agent evaluations were run for this audit. Automatic CI passed on `4591940169`, including package/API tests, all six browser-test shards, visual regression, typechecks, lint, and generated-file checks. Coverage additions are pushed in `2371d9796a` and `4591940169`: check actual text sent to persistence; duplicate ordinary props across instances; rejected edits leaving capabilities reusable; merged patch ordering and input immutability; accepted/rejected linked Image dimensions in MDX templates; returned style/source/selection state; and UI Image dimension changes followed by reload. The previous image E2E used equal-sized files and could miss broken dimension synchronization. Its new SVG has different dimensions. Production code is unchanged. No tests or evaluations were run for this audit, as requested; no new pass claim is made. - [x] Inventory every changed/moved/removed production function and map it to assertions - [x] Add missing mixed-child and dependent-transaction coverage - [x] Push inventory follow-up and inspect CI results ### Function-by-function coverage inventory This inventories production functions changed, moved, or removed by this PR. “Covered” means the named test asserts the behavior; it does not claim every possible input or branch is covered. No local tests or evaluations were run for this inventory. | Function / handler | Assertions traced | | --- | --- | | `renderProperty` and its `onChange` callback | Content-mode props E2E checks text, href, Image/Video source, alt, protected controls, and reload. Asset E2E now changes dimensions from 120×80 to 240×160, then replaces/reloads to 120×80. | | `createContentModeCapabilitiesFromBuild` (moved unchanged) | API “commits content-mode mutations with editor api tokens” and “limits editor api patches to content instances and props” exercise conversion from saved build data. | | `assertContentOrBuildTransactions` | Editor vs Builder, design denial, transaction/payload limits, all-or-nothing rejection; new exact 10-transaction boundary and dependent append-then-edit transactions. | | `contentOrBuildMutation` | API semantic text edit asserts version, authorization, actual saved text, unchanged outside content, and unchanged input. | | `build.patch` handler | API tests assert allowed text forms, denied structure/styles/assets/expressions/outside roots, Viewer/no-API denial, stale retries, current-version validation, and future-version CONFLICT before persistence. | | `getContentModePropNamesByTag` (moved unchanged) | Derives names by tag and merges names for matching tags. | | `getDefaultContentRootIds` (moved unchanged) | Default vs explicit roots, Body selection, template exclusion. | | `getContentModeFrontmatterBoundTargets` (moved unchanged) | Explicit Body/frontmatter scope, read vs readwrite expressions, recursive instances. | | `getContentModeEditableInstanceIds` (moved unchanged) | Outside roots, nested templates, missing references/roots, non-id children, cycles. | | `getContentModeEditableInstanceRootIds` and its recursive `visit` (moved unchanged) | Same traversal cases; transaction tests reject moving content between roots and duplicate references. | | `getInstancePropMetas` (moved unchanged) | Tag-derived props, unknown/design-only/incompatible props, no generic HTML-attribute permission bypass. | | `getContentModeEditableStaticPropNames` (moved unchanged) | MDX template tests assert exact editable names, primitive conversions, and retained/ignored design-only props. | | `getContentModePropEligibility` | Matching/missing/different asset sources, protected asset props, exact primitive types, UI/transaction parity, Image vs Video. | | `isContentModeCopyableProp` (moved unchanged) | Editable/copyable classification and content-mode copy-policy test. | | `getContentModeCapabilities` | Editable IDs, roots, frontmatter bindings, asset metadata; shared UI and transaction eligibility test. | | `isContentModePropTransition` | Schema/id/owner/name checks, unknown instance, protected existing props, permitted source type changes, linked dimensions. | | `validatePropPatches` | Add/replace/remove, new-then-update, missing replacement target, protected ID reuse, copied template defaults, final-state media patches in split/reversed order, failed-edit immutability and retry. | | `hasSameInstanceMetadata` | Whole-instance text replacement allowed; tag/name changes and outside references rejected. | | `validateStyleSourceSelectionPatch` | New content selection allowed; existing/token misuse and dangling cleanup rejected; now asserts resulting selection map. | | `validateStyleSourcePatch` | New local source allowed; token, duplicate/unused source and incomplete cleanup rejected; now asserts resulting source map. | | `validateStylePatch` | New local declarations allowed; existing design styles, missing breakpoint and incomplete cleanup rejected; now asserts resulting declaration map. | | `validateMapChange` | Above three map paths, invalid patch targets, resulting state and input immutability. | | `validatePatchChange` | Instance/prop/style/asset/page paths and rejected design namespaces through transaction tests. | | `preservesNonTextChildren` and local `keepNonText` | Text-only changes allowed; insertion/deletion/reordering of instance references and expression changes denied; new mixed text/reference positional cases. | | `applyContentModeTransaction` | Authorization/persistence parity, split batches, empty transactions, scoped permissions, copy protections, roots, cleanup, and immutable input on success/failure. | | `materializeMdxAuthoredContent`, its `visit`, and `overlayChildren` | MDX-source “round-trips an authored Asset Image through a styled composite descendant” asserts src/width/height and styles after reload; “serializes an Asset selected on an inserted Image template” asserts all four linked props; nested and standalone component-permission tests reject protected props. | | `getMdxPropEligibility` | Template tests check allowed/ignored props, aliases, incompatible types; new matching vs mismatched Image dimension asset test exercises passed asset values. | | `materializeMdxTemplates` | New linked-dimension test plus existing copied-fragment, unchanged-input, authored-prop, diagnostic, and alias tests. | | `appendBuilderPatchChanges` | Mutation accumulator test asserts sequential state and combined payload; merge test asserts ordering and input preservation. | | `mergeBuilderPatchChanges` | New repeated/interleaved namespace, empty input, repeat-call and patch-array immutability assertions; permission tests exercise split payload integration. | | `getMediaAssetPropNames` | Image generates width/height/alt; Video and unrelated components do not; explicit overrides and expression-bound metadata retained. | | `getPropIdsToDelete` | Image src removes dimensions; non-Image removes only requested prop; resource cleanup and missing-instance cases. | | `updateProps` | Atomic Image/Video updates, repeated sources with/without saved props, overrides, bindings, original-state preservation; new ordinary prop deduplication across names and instances. | | `getPatchParent` (type-only change) | Mutable nested patch and missing-path tests. | | `assertReplacePatchTargetsExistingPath` (type-only change) | Missing target rejected; preceding add/root replacement makes later replacement valid; preceding remove makes it invalid. | | `applyBuilderNamespacePatches` | Sequential/root replacements, append then edit, remove then replace, immutable state, permission/persistence parity. | Removed functions are covered through their replacements: `shouldSyncMediaAssetProps` → shared `updateProps` plus UI E2E; `assertContentOrBuildPayload` → transaction adapter; `isContentModePropForInstance` → eligibility; `isContentModePropPatchValue`/`validatePropPatch` → transition/batch validation; `applyValidatedInstancePatch` → shared patch engine. No behavior test is needed for import-only changes, type declarations, public-entry exports, or regenerated metadata/docs; their callers and contracts retain tests. The original reporter-specific UI reproduction and image-description product question remain separate limitations. This inventory does not claim either is resolved. ### Latest review follow-up - [x] Reject fresh prop IDs that shadow existing instance/property records; cover protected bindings and valid edits - [x] Coalesce updates in linear time while preserving last-update order and input immutability - [x] Check documentation/fixture impact, push, inspect CI, and reply to both review threads Review fixes pushed in `5dd03135e7`: 10 net production lines. Added binding-shadow and batch-work regression assertions; no local tests or evaluations were run, per request. Formatting and diff checks passed. Documentation and generated fixture inputs/outputs are unchanged. Automatic CI is pending. - [x] Reject duplicate fresh props within the same transaction and test split/combined batches - [x] Preserve resource and parameter media bindings using the existing dynamic-type check - [x] Bound total Editor patches before validation; test the boundary and unchanged Builder access Review follow-up `d478446eb1`: all package, Builder, and CLI CI test jobs and typecheck/lint/generated checks passed. Five addressed review threads are resolved. Browser CI checks are still running. One newly posted review about non-image assets assigned to Image sources remains open pending the user's behavior choice. No local tests or agent evaluations were run; local typechecks passed. ### Shared asset constraints - [x] Reuse component file-control metadata and the UI MIME matcher in shared property writes; preserve manually entered URLs and pending uploads - [x] Generate linked Image metadata only when the selected asset supports it; cover legacy file-backed images - [x] Add shared-runtime coverage for accepted/rejected Image and Video assets, custom props, URLs, and atomic rejection - [x] Review documentation, generated contracts, fixture impact, and diff size; verify typechecks and CI (no local tests or evaluations, as requested) Shared asset follow-up `5f0054edd7`: property writes use the UI's existing file-control `accept` metadata and MIME matcher. Known incompatible assets are rejected with the approved message. Legacy image files remain selectable without generating unsupported metadata. URLs, custom asset props, and unresolved uploads keep their existing behavior. Asset records are now loaded before CLI/MCP property writes; this may require an asset-list request when not already loaded. This change does not add asset-file downloads. Net handwritten production change: 24 lines. Builder/project-build typechecks, changed-file lint, formatting, and diff checks passed. No local tests or evaluations were run. Regression and runtime-entry tests were added; CI is pending. Reviewed Image/Video docs: no guidance change needed. No fixture inputs or generated fixture bundles changed; regenerated runtime contract metadata only. - [x] Keep Editor permission checks when a shared edit operation loads assets; add an API regression test before final CI verification Follow-up `d33b70e1c7` preserves Editor authorization when property writes load assets. The API regression test asserts a valid Image source reaches persistence, incompatible files are rejected, and outside-content edits remain forbidden. This reuses the existing asset loader and permission adapters (3 net production lines). CI found and corrected an old session namespace expectation and a mistaken test assumption: Video's existing unsupported `.mpg`/`.mpeg` accept entries cause the shared matcher to allow all assets. This PR preserves that UI behavior; it does not tighten Video formats. Total handwritten production increase for this asset follow-up: 27 lines. Builder typecheck/lint and server metadata generation passed; metadata output was unchanged. CI is rerunning. Final asset follow-up verification (`8b2efd8508`): all package, CLI, Builder API/unit, and six browser E2E jobs passed, together with lint/typechecks, generated checks, visual regression, migrations, size, packaged SSG, and branch deployment. The direct API test now checks the typed authorization cause, not an HTTP status that this test caller does not map. All review threads are resolved. No local tests or agent evaluations were run for this follow-up; CI provided execution coverage. The separate Video filter limitation noted above remains unchanged. ### Shared write-boundary validation - [x] Reuse one asset-property validator in UI/MCP property updates and raw patch persistence, independent of permission level - [x] Cover whole-property and field patches, component changes, invalid metadata, final-state validation, unchanged legacy data, and stale retries - [x] Review documentation/fixture impact, typecheck and lint, then verify CI; no local tests or evaluations per the earlier request Expected change: about 70–90 net production lines. Patch saves load only referenced asset metadata; no asset-file downloads. Shared-save follow-up: 133 net production lines, above the initial estimate because one save can replace an asset file and its references together. The save validator reuses the asset writer’s canonical file preparation rather than trusting client-declared asset types. It also checks asset-only replacements, before folder or Build writes, while leaving unchanged legacy references alone. Ordinary property writes load only referenced asset metadata; asset-changing saves with references additionally prepare the full asset map, then the writer repeats that metadata preparation. No file contents are downloaded by the new check. This does not change schemas, migrations, generated contracts, documentation, or fixture inputs/outputs. Added regression coverage; no local tests/evaluations were run, per the earlier request. Independent Astra review found no remaining defect after the projection fix. CI pending. - [x] Fix selective UI sync loading to include asset-validation dependencies; add selection regression coverage and rerun CI The first browser run caught missing props/instances in selectively loaded UI saves. Package/API tests passed but their fixtures provided complete build rows. The development deployment succeeded; its status-reporting step failed with GitHub HTTP 500. Final verification on `a22d059717`: all package, CLI, Builder unit/API, six browser shards, visual regression, typecheck/lint/generated checks, migrations, size, packaged SSG, development deployment, and Ubuntu/Windows deployment fixtures passed. Browser shard 3 passed on one retry after a page-drag ordering assertion; the save/parsing errors were fixed by loading the required fields. No local tests or evaluations were run. The added persistence suite has 46 passing tests and runtime props has 74; selective-sync tests now omit unselected fields. No unresolved review threads. Total net production increase across this follow-up: 134 lines. ### Deep review follow-up - [x] Make MDX linked-image validation independent of authored attribute order; cover changed sources, mismatches, and the affected materialization paths - [x] Review DRY and diff size, documentation/fixture impact, and independent review findings - [x] Verify typechecks/lint and CI; no local tests or evaluations per the earlier request Review found that linked dimensions can be checked against the old template source before the new source is applied. Fix should reuse the shared eligibility rule with the final source, without another property parser. Initial estimate: under 60 added non-test lines; revisit the approach if it needs more. Deep-review fix `f7c3d805a8`: apply Image sources before linked metadata in all three MDX materialization paths, keeping original attribute indexes for asset references. Reuses the existing eligibility checks and parser; 23 net production lines. Regression cases cover source order, changed sources, mismatched dimensions, and nested overlays. Independent Astra review, project-build typecheck, focused lint, formatting, and diff checks passed. No schema, documentation, generated contract, or fixture-data changes. Local tests/evaluations have not run; focused-test permission was requested. CI pending. Final deep-review verification on `f7c3d805a8`: all package/CLI/Builder test jobs, all six browser shards, visual regression, typechecks/lint/generated checks, migrations, packaged SSG, size, submodules, and development deployment/fixtures passed. CI ran 60 authored-content and 24 template-materialization tests successfully. No local tests or evaluations ran; fail-before/pass-after was not executed locally. All review threads are resolved and the worktree is clean. No further actionable defect was found in the independent reviews. Known remaining cost: asset-changing saves can prepare metadata twice; removing that safely would expand this fix into persistence changes. ### Editor MCP browser E2E follow-up `0ee978e465` adds a real CLI/MCP Editor-token round trip: insert content into a Content Block, reload through a fresh CLI process, verify it in the Builder canvas, and reject a protected template insertion without changing the saved Build. A direct API denial now returns `FORBIDDEN` instead of a generic internal error. The focused E2E and API tests, typecheck, lint, formatting, and diff checks passed locally. Fail-before/pass-after was verified by temporarily disabling Editor fragment access, then restoring it. The full Luna/low evaluation run was attempted but did not pass: the form-runtime fixture received 503 responses. The suite is not claimed green. CI for this push passed on `0ee978e465`, including all six Builder E2E shards, package/CLI/Builder tests, visual regression, typecheck/lint/generated checks, migrations, size, SSG build, and submodules. Live MCP permission probe against the branch deployment: Builder, Editor, and Viewer role metadata matched their tokens. Editor content text, rich text, fragment, link href, and compatible Image source edits committed and survived a fresh MCP session. Outside-root, protected-template, design-only prop, mismatched Image width, and incompatible asset writes were denied without persistence. The updated deployment returned structured `FORBIDDEN` for protected content writes even when `contentMode` was false or omitted. Mixed valid/invalid writes were atomic; stale Editor writes refreshed and retried. Viewer MCP could not be exercised beyond the API gate because this Viewer token has API access disabled. MDX frontmatter, token revocation, and UI-only scenarios were not tested. A wrong-project token was rejected but surfaced `INTERNAL_SERVER_ERROR`, which is a diagnostic-quality gap, not an authorization bypass. The isolated probe page `/codex-permission-probe-6575` remains: cleanup was stopped after a concurrent-edit `CONFLICT`, to avoid deleting another session's work. ### Fixture-based parity follow-up - [ ] Verify the full Content-mode UI/MCP parity matrix with an isolated MDX/template/media/page fixture, excluding publishing and token/plan changes - [x] Fix the reproduced Editor MCP template/page-settings gap and cover fail-before/pass-after routing - [x] Verify a real CLI Editor-token create-from-template call, fresh readback, Builder reload, allowed settings, and protected-setting rejection - [x] Update MCP guidance, review fixture impact, and verify required CI The UI already allows an Editor to create a page from a template and edit allowed page settings. MCP incorrectly required Builder permission, and a separate MCP-only legacy Webhook Form copy guard blocked the seeded template. Those restrictions are corrected. The server now derives Content-mode page copying from Editor permission even when `contentMode` is omitted or false; Builder copies retain their existing behavior. Editor semantic commands use the server's shared Content-mode validator, while raw Editor patches stay limited to text and editable props. This adds a named server call and refetch for Editor semantic edits; the existing initial project sync still occurs. The new isolated E2E passed on `03e7ca6953`, including flag-free template creation, settings edit, fresh CLI readback, Builder reload, and a denied protected setting without a saved change. All required CI passed on that commit: all six Builder E2E shards, package/CLI/Builder tests, generated checks, typecheck/lint, visual regression, migrations, packaged SSG, size, and submodules. Focused local runtime/API tests, typechecks, lint, formatting, and generated-doc checks passed. Browser E2E and full agent evaluations were not run locally; the requested browser approval and a fresh evaluation approval were not received. The full UI/MCP parity matrix remains open; Editor asset operations are a separately identified gap, not claimed fixed here. ### Exploratory permission coverage follow-up - [x] Inventory observed Builder/Editor/Viewer UI and MCP permission scenarios against existing tests - [x] Add fail-before/pass-after regression tests for confirmed uncovered failures - [x] Run focused tests, typechecks, lint, and fixture checks; verify required CI - [x] Record unsupported UI/MCP parity as open product work, without calling the matrix complete The UI share-link E2E already checks Viewer read-only controls, Editor text editing, and Builder design controls. Existing MCP E2E checks Editor content/template writes, page creation, asset upload, and protected edits. This follow-up adds one real CLI/MCP flow with API-enabled Viewer and Builder tokens: Viewer can list pages but cannot create a page or asset folder or upload a file; Builder can change an HTTP status setting that Editor cannot. The test checks the denied page writes leave the build unchanged and the Builder setting survives readback. The run exposed two diagnostic bugs: local permission denial and REST asset-upload denial reached MCP as generic failures. Both now retain `FORBIDDEN`. Focused unit tests failed before these changes and pass after; the full local browser/MCP test passes. The only non-test change is three net lines. No fixture data, schemas, generated contracts, or product docs changed. Local checks passed: 2,654 project-build tests, 48 HTTP-client tests, the focused Builder E2E (2 tests including setup), affected typechecks, lint, formatting, and package boundaries. `check:fixture-sizes` could not run locally because the SSG fixture's built `dist/client` is absent; CI builds that fixture before checking its size. The full UI/MCP parity matrix remains open. In particular, Editor UI asset update/replacement/duplication/deletion and collection-entry creation still lack equivalent MCP support until the UI's collection protections are enforced at the shared server boundary. Publishing and token/plan changes were outside this test scope. CI passed on `6490efc838`: all six Builder E2E shards, package/CLI/Builder tests, visual regression, typecheck/lint/generated checks, migrations, packaged SSG, size, and submodules. Shard 3 first hit Playwright's 120-second limit under four-worker load; its logic passed locally. The longer role flow now uses the same 240-second per-test limit as other MCP E2E tests, and shard 3 passed in the final run. ### Final UI/MCP parity audit - [x] Trace Editor UI asset and folder actions through REST, runtime, and persistence - [x] Reproduce Editor MCP upload and folder-create denials; add fail-before/pass-after permission tests - [x] Add isolated Editor MCP upload/folder and UI folder-reload browser regressions - [x] Keep collection-sensitive asset/folder mutations Builder-only until UI collection protections are shared at the server boundary - [x] Review docs and fixture impact: MCP guidance is updated; no fixture input or bundle changes - [x] Restrict Editor folder saves to new IDs; test overwrite and delete denials at both UI and API boundaries - [x] Reject folder-add collisions at persistence and test the mixed-writer/stale-snapshot case - [x] Keep exact Editor folder retries authorized after a partial save; add fail-before/pass-after coverage - [x] Verify the new browser E2E in required CI - [ ] Run the full Luna/low agent evaluations after fresh approval (not granted for this follow-up) - [x] Verify required CI for `6fd3beefc8` The safe scope is small: the Assets REST upload path already authorizes Editor tokens and enforces collection rules, but the MCP catalog incorrectly required Builder access. Creating an ordinary asset folder is available in the Editor UI, but the shared Content-mode save check rejected it. Both now use Editor permission. The isolated browser tests check fresh MCP readback and Builder reload; they have not run yet. Further parity work remains open: Editor UI allows updates, replacement, duplication, deletion, and collection-entry creation in cases MCP cannot currently perform. These need the UI's collection protections enforced at the shared server boundary before their MCP permits can be widened. Raw Editor `apply-patch` stays text-and-props-only. This PR does not claim the full matrix is complete. Asset/folder follow-up `6fd3beefc8`: Editor semantic tools now share the UI's edit permission for uploads and ordinary folder creation. Folder saves reject same-ID changes, allow identical retries after a lost response, and use insert for add-only persistence. The retry regression failed before the fix and passes with JSON-normalized parent IDs/timestamps. Independent Astra review found no remaining concrete defect. Focused runtime (86), asset persistence (52), protocol (14), and API/sync (71) tests passed; Builder/project-build/asset-uploader typechecks, lint, package boundaries, formatting, generated API, and MCP docs checks passed. Full project-build, protocol, and CLI suites passed earlier in this follow-up; Builder core had resource-contention failures but its affected tests passed in isolation. The new browser E2E and agent evaluations were not run locally pending fresh approval; CI is running them automatically. CI passed on `6fd3beefc8`: all six Builder browser shards, package/CLI/Builder tests, generated checks, typechecks, lint, visual regression, migrations, packaged SSG, size, and submodules. The new Editor MCP asset round trip and UI folder-reload tests ran in the passing browser shards. |
||
|
|
dabaeede02 |
fix: Hide deprecated components in Builder (#6576)
The Components panel now hides deprecated components by default. The default-off `deprecatedComponents` feature flag shows all nine registered deprecated components in a separate **Deprecated** section, including Webhook Form and RemixForm. Webhook Form keeps its saved component ID, icon, behavior, navigator label, and `w-webhook-form` CSS class. The command picker continues to hide deprecated components. ## Checklist - [ ] Document each feature flag beside its declaration using its current behavior. - [x] Keep Webhook Form metadata and picker guidance pointing to Form. - [x] Preserve the generated `w-webhook-form` class without custom `presetClassName` metadata. - [x] Hide deprecated components from the Components panel by default; show all nine eligible components in a dedicated Deprecated section behind the default-off `deprecatedComponents` flag. - [x] Keep deprecated components out of the command picker. - [x] Add focused tests for default and flagged visibility, section membership, exclusions, and template insertion data. - [x] Review documentation impact. Existing Webhook Form docs describe its default unavailability; the flag is for development, so no docs change is needed. - [x] Verify focused tests, typechecks, lint, and fixture generation. The fixture pipeline passed; its unrelated live fixture path drift was restored. - [x] Update the Components panel E2E filter assertion for the flagged deprecated section and verify the rerun. - [x] Verify required CI on the latest head. Focused tests: component catalog 16/16, Builder 3/3. Builder, project-build, and feature-flags typechecks passed. Lint and the full fixture pipeline passed. The new section tests failed as expected with section creation temporarily disabled and passed after restoration. Latest-head CI: Main workflow, Visual regression, Lint PR, and Check submodules all passed, including all six Builder E2E shards. Ref #4093 |
||
|
|
534401c66b |
fix: keep publish progress after closing popover (#6570)
## Summary - Show the existing validation success toast if validation finishes after the Publish view closes or switches to Export. Keep it quiet while the result is visible in Publish. - Keep publish progress running when the popover closes. Reopening shows a pending state without restarting the countdown and updates when publishing finishes. - Show a completion toast only after the Publish popover closes. Keep errors and diagnostic findings visible across panel remounts. - Continue polling a recovered pending build after the popover closes. Ignore old status responses and superseded manual validation results. - Keep a manual validation report and its “See issues” button available after closing and reopening the Publish popover. ## Checklist - [x] Consolidate publish progress and validation state. The PR's net non-test change is down from +560 to +457 lines. - [x] Cover the close/reopen validation result and suppress duplicate publish-error toasts while Publish is open. - [x] Run focused tests, Builder typecheck, lint, formatting, and required CI after the simplification. - [x] Show the existing validation success toast when validation finishes outside the Publish view; suppress it while the inline result is visible. - [x] Prove the open and closed outcomes with a failing-then-passing regression test. - [x] Clear a persisted status error when a successful refresh finds no build, while preserving request errors. - [x] Prove the no-build case with a failing-then-passing regression test and recheck docs, fixtures, and 55 focused tests, typecheck, lint, formatting, and diff checks. - [x] Show validation completion when the user switches to Export before the result arrives, with a failing-then-passing regression test. - [x] Resolve reviews and pass CI on commit |
||
|
|
bc7e58af84 |
fix: Stabilize Preview and MCP issue reports (#6572)
## Problem A fresh iterative Preview can lose its rendered content on the first client navigation with a React `useContext` error. MCP issue reports can also miss the page or instance ID needed to locate a problem, and concurrent tool calls can overwrite newer report context with an older snapshot. ## Fix - Have Vite scan generated routes before the first navigation. This applies to the base React Router template and the Netlify and Cloudflare overlays. It changes local iterative Preview, not saved project data or production routing. - Include up to three recent, validated tool targets in MCP issue reports. A project-relative screenshot path is matched to its page ID; the CLI does not store the path, full URL, token, or page content. It cannot read the Builder's current UI selection, but includes an instance ID when a tool call names one. - Treat structured MCP error results as failures and serialize report-state writes within a CLI session, so a concurrent older write cannot replace a newer result. The reporting work from #6574 is now included here. #6574 is superseded. ## Verification - Reproduced #6569 twice with CLI 0.306.0 and a cold optimizer cache. With the Vite change, a cold `/` → `/blog` navigation and direct article load rendered without browser errors. - The config assertion failed without the Vite entry and passed with it. A proposed optimizer test also passed without the fix, so it was removed; the real cold-navigation check remains the behavioral evidence. - The new concurrent-write test passed with serialization and failed when the queue was temporarily removed. All 106 focused CLI tests, CLI typecheck, root lint, formatting, and generated API check passed on the combined branch. - Earlier checks of the same Preview and reporting code passed: 133 prebuild tests, 144 project-build MCP tests, protocol and Builder reporting tests, affected typechecks, CLI build, generated docs, and CI on the separate PRs. - The fixture build passed without changing tracked files. The full local fixture pipeline stopped at sync because the development server returned bundle version `bundle-17o6sch` while this checkout expects `bundle-1sh9iaz`. The combined commit passed Linux and Windows fixture checks and all other required CI jobs. - Full Luna/low agent evaluations were run earlier on the combined branch: the latest run passed 8 of 9 fixture tasks, while the managed-form assertion failed on an unrelated task. Saved baselines were missing or incompatible, so the command failed overall. No baselines changed. Evaluations were not rerun for this final concurrent-write change. ## Checklist - [x] Reproduce and fix the first-navigation Preview failure - [x] Cover the base, Netlify, and Cloudflare templates and generated fixtures - [x] Include safe recent page and instance IDs in MCP issue reports - [x] Treat structured MCP errors as failures - [x] Serialize concurrent report-state writes and verify the regression test fails without the queue - [x] Review documentation impact: Preview usage and user guidance are unchanged; no docs change - [x] Run focused local checks and attempt the fixture pipeline - [x] Confirm required CI passes on the final combined commit Closes #6569 Ref #6571 |
||
|
|
13eb7527ef |
fix: route deprecated Webhook Forms through Form backend (#6553)
Deprecated Webhook Forms reuse the existing Form execution pipeline without teaching the modern backend about WebhookForm. ## User-facing behavior and how to test it - Existing saved Webhook Forms keep their markup, action settings, and success/error feedback. Submit a published Webhook Form and check both outcomes. - URL and Resource actions preserve configured methods, headers, dependencies, body overrides, and the last submitted value for repeated field names. Test a configured action and inspect the received request. - Default owner notifications use the Email Worker, including submitted files. Submit a Webhook Form without a custom action and inspect the owner email and attachment. - Configured Email actions validate recipients and retain threading metadata. Test valid recipients and a rejected recipient limit. ## Implementation - One deprecated Webhook Form adapter calls existing bot validation, Email preparation, recipient validation, resource loading, and response handling. - Both generated route templates call that adapter. Both paths reuse an internal resource-graph traversal helper; modern submission behavior is unchanged. - Root actions use the protected, uncached request override. Dependencies reuse existing generated Assets and Resource caching behavior. - Keep reachable-resource filtering because Email preparation runs before loading: unrelated Email settings must not break the selected action. - Remove dead `getDefaultActionResource` wiring from generated templates. ## Verification - Focused SDK tests: 116 passed (11 deprecated Webhook Form adapter and 105 modern submission, Email, and handler tests), including unrelated resources, missing dependencies, cycles, Email preflight/threading, and single-attempt Email failures. - Resource generator tests: 26 passed. - Full CLI prebuild suite: 139 passed after tracked fixture regeneration; focused deprecated Webhook Form adapter tests: 9 passed. - Repository fixture pipeline (`pnpm -r fixtures:build`) passed. All tracked generated `getDefaultActionResource` references were removed, and no Builder `N8N_FORM_EMAIL_HOOK` reference remains. - Final MCP-generated local Preview E2E rerun: 3/3 passed. - Focused saved Webhook Form custom-action E2E rerun: 2/2 passed (setup and test), including a single POST through the protected test receiver after Builder reload. - SDK, CLI, and Builder typechecks, focused lint and formatting, and `git diff --check` passed. - Deprecated component picker regression test passed (2/2); verified it fails when WebhookForm deprecation is disabled, then passes with deprecation restored. - Independent Astra review found no blocker, including the 62 generated fixture changes. No production deployment or live secret change was performed. - Required staging CI checks passed on previous head `15635fc` (Main workflow, visual regression, submodules, and lint). An unrelated existing MCP success-redirect E2E assertion failed once in shard 5, passed in a focused local rerun (2/2 including setup), then shard 5 passed on GitHub rerun without code changes. - Terminology pass (`4a69ab573`): renamed the dedicated SDK adapter and migration helper, updated both route templates and generated fixtures, corrected the active identity migration marker comments, and synchronized docs and test names. Focused tests passed: SDK 35, CLI prebuild 12 plus 2 after final title change, MCP 7, migrations 8, React browser 7, CLI docs 9. Seven affected package typechecks, fixture builds, formatting, and `git diff --check` passed. That historical head was superseded; latest-head CI results are recorded below. - ResourceForm identity migration (`29a21edbb`): focused migration (23), DB patch (25), project-build DB/session (93), Builder API/import/auth/patch (60), React SDK (162), runtime/MCP (239), registry (11), CLI evaluation (90), CLI session/sync (55), and protocol (15) tests passed; full CLI prebuild (139) passed. Affected typechecks, lint, formatting, and `git diff --check` passed. Independent Astra review found no blocker. No deployment was performed. - Prepublish Webhook Form warning (`25855e8f00`): audit checks every non-draft published page and links nested/shared instances to the correct page; modern ResourceForm, native forms, draft-only forms, and orphans are excluded. Focused audit tests (13), Builder navigation tests (6), project-build and Builder typechecks, lint, formatting, and `git diff --check` passed. Independent Astra review found no blocker; publishing remains nonblocking. No deployment was performed. - ResourceForm test and CI expectation repair (`e1a527fbcc`): focused React SDK (24), Builder preview/registry (38), project-build pages (1), and CLI fixture API (8) tests passed; Remix and React Router targeted browser suites, affected package typechecks, full formatting (3375 files), lint, and `git diff --check` passed. Astra review found no blocker. Persisted identity and runtime exports were unchanged. - Project bundle fixture repair (`70889ef27d`): eight bundles now carry the current contract version; the two unmarked historical Form instances in `webstudio-features` normalize to WebhookForm. Protocol fixture tests (17), protocol typecheck, CLI fixture build, formatting, lint, and diff check passed. Independent Astra review found no blocker. - Story/MCP E2E CI repair (`483614e6c5`): SDK CLI story tests (7), ResourceForm fragment tests (34), and focused local managed-form E2E (4 including setup) passed. SDK CLI and Builder typechecks, lint, formatting, and diff check passed. Astra review found no blocker. The initial local Preview build used an ignored stale React Router `lib` artifact; rebuilding it restored the passing E2E run. - Latest PR head `483614e6c5`: Main workflow [run 39317777032](https://github.com/webstudio-is/webstudio/actions/runs/39317777032) passed, including all package matrices and six Builder E2E shards. Visual regression [run 39317796922](https://github.com/webstudio-is/webstudio/actions/runs/39317796922), submodules, and PR lint checks passed. The overlapping unapproved visual run was cancelled by the approved rerun. - Shared bot-value generator (`ecc4cad1e9`): SDK helper tests (3), React Router WebhookForm/ResourceForm browser tests (5), SDK/Router/Remix typechecks and builds, SDK browser-boundary check, formatting, and diff check passed. Brave bypasses media queries, working media queries produce a fresh timestamp per submission, and failed queries produce `jsdom`; each managed/legacy submission has one bot field. Independent Astra review found no blocker. Latest-head Main workflow [run 39325677117](https://github.com/webstudio-is/webstudio/actions/runs/39325677117) passed, including all six Builder E2E shards; approved visual regression [run 39325702835](https://github.com/webstudio-is/webstudio/actions/runs/39325702835), submodules, and PR lint passed. The superseded visual run was cancelled by the approved rerun.\n\n- Historical Form clipboard guard (`e864283d1c`): instance/page transfer parsers reject ambiguous legacy `Form` before asset import; direct fragment/page/folder mutations preflight before changes. Canonical ResourceForm, WebhookForm, and NativeForm remain accepted. Project-build tests (89), Builder paste browser tests (69), both typechecks, focused lint/formatting, and diff check passed. Independent Astra review found no blocker. Latest-head Main workflow [run 39329184890](https://github.com/webstudio-is/webstudio/actions/runs/39329184890) passed, including all six Builder E2E shards; approved visual regression [run 39329562692](https://github.com/webstudio-is/webstudio/actions/runs/39329562692), submodules, and PR lint passed. The earlier unapproved visual run reported two expected story differences.\n\n## Compatibility notes - Root HTTP actions reject private and same-origin destinations. Dependencies retain their existing fetch/cache behavior. - Routes retain the 25 MiB request limit. Shared execution applies bot validation, a 10-second Resource timeout, and one retry for eligible HTTP failures; an ambiguous failure can duplicate an HTTP action. Email actions are not retried. - Default email uses the Email Worker's sender, plain text, recipient limits, and attachment limits instead of the legacy n8n HTML/basic-auth path. - Self-hosted sites need normal Email sender configuration. Direct Builder draft Preview handling is outside this published-route change. - Coordinate this CLI release with [SaaS PR #669](https://github.com/webstudio-is/webstudio-saas/pull/669), which removes the n8n helper and `N8N_FORM_EMAIL_HOOK` from new published-site builds. Publish with a CLI artifact containing this adapter. Existing deployed workers retain their bundled code and injected hook until republished, so keep the actual n8n endpoint available while those sites remain active. ## Implementation checklist - [x] Align saved Webhook Form custom-action E2E with the protected receiver and verify one submission. - [x] Regenerate tracked published-site fixtures with the updated CLI and confirm no `getDefaultActionResource` or `N8N_FORM_EMAIL_HOOK` references remain in Builder source/config/fixtures. - [x] Review authoritative `docs/` impact of removing the obsolete Builder hook wiring; related deprecation and integration docs were updated. - [x] Route deprecated Webhook Form submissions through the shared execution pipeline with one adapter. - [x] Preserve configured actions, dependencies, repeated fields, body overrides, and saved feedback. - [x] Prepare and validate Email actions using existing shared logic. - [x] Remove duplicated orchestration and dead generated context wiring. - [x] Verify generated routes and MCP-generated Preview submissions. - [x] Mark the Webhook Form component and its dedicated dependencies visibly deprecated, while leaving shared Form infrastructure active. - [x] Group Webhook Form-only SDK code for straightforward removal while preserving public APIs and generated output. - [x] Use deprecated terminology consistently for Webhook Form-only PR code, and clarify the active identity migration marker. - [x] Confirm required staging CI checks. - [x] Use ResourceForm as the one active modern component identity across Builder, SDK, runtime, templates, generated code, and docs. - [x] Migrate historical Form, NativeForm, ResourceForm, and WebhookForm identities in one idempotent pass using the historical pages marker. - [x] Remove the marker from ordinary project and snapshot APIs while preserving historical input readers. - [x] Persist pages and instances together when a historical marked project is saved, including a pages-only edit. - [x] Verify migration, export/reimport, old CLI and server snapshots, page-only save, generation, and affected typechecks. - [x] Complete an independent architecture review before commit or push. - [x] Warn at prepublish for deprecated Webhook Forms on published pages, with a link to each affected element. - [x] Verify modern ResourceForm is excluded and nested/shared Webhook Forms navigate to the correct page. - [x] Complete independent review, focused tests, and affected typechecks before push. - [x] Remove local `ResourceForm as Form` aliases from React SDK tests and fixtures; use the canonical component name throughout. - [x] Fix stale identity expectations found in CI and verify affected tests/typechecks. - [x] Complete independent review before push. - [x] Refresh tracked project bundle fixtures to the current bundle contract and canonical WebhookForm identity; verify protocol and affected CLI flows. - [x] Align SDK CLI story assertions and MCP E2E fragment fixtures with the persisted ResourceForm component name; rerun affected checks. - [x] Share one browser bot-value generator across ResourceForm submission and deprecated Webhook Form wrappers, preserving field ownership and behavior. - [x] Verify bot timestamps, Brave/media failure handling, repeated submissions, affected typechecks, and independent review before push. - [x] Reject ambiguous historical `Form` clipboard fragments before any insertion or transfer mutation, with clear recopy guidance; preserve canonical ResourceForm action remapping.\n- [x] Verify instance/page/template/folder paste regressions, independent review, and latest-head CI.\n\n - [x] Explain Email attachment size limits in the Email action UI and give a useful error when the aggregate encoded attachments exceed the local guard. - [x] Check Preview and published behavior, boundary tests, and authoritative Form docs; verify the focused change and get independent review. Ref #4093 - [x] Preserve safe email codes/messages through MCP, including local attachment and upload limit errors. - [x] Correct Form documentation for each enforced limit and verify Preview/published/MCP paths. - [x] Verify Builder SDK and MCP focused tests, typechecks, and independent review before push. - Email limits and failure copy (`6d215f3ec3`): the shared SDK checks 32 attachments and 7 MiB aggregate Base64 data before dispatch; the 25 MiB multipart response and Email action guidance use approved copy. Managed Email error codes pass through Form and MCP results with resource IDs, while MCP still omits resource bodies. Form docs distinguish the Worker 7 MiB JSON cap from Cloudflare's 5 MiB complete message and verified-destination exception, and correct the no-retry behavior. Focused SDK tests 89, Builder API tests 44, UI browser tests 42, SDK/Builder/CLI/docs typechecks, lint, formatting, and generated MCP docs check passed. Astra found no blocker. No real email or production deployment occurred. - Latest Email limits head `6d215f3ec3`: Main workflow [run 39393037290](https://github.com/webstudio-is/webstudio/actions/runs/39393037290) passed on rerun, including all six Builder E2E shards; approved visual regression [run 39393456618](https://github.com/webstudio-is/webstudio/actions/runs/39393456618), submodules, and PR lint passed. The initial unapproved visual run showed the same two previously accepted story differences. - [x] Address review: normalize previous-Builder `Form` instance patches before persistence, preserve historical WebhookForm identity, and cover marked/unmarked cases. Documentation impact reviewed: this restores intended save behavior, so no user guide change is needed. - Review finding follow-up (`ce05dd733a`): three Form identity patch regressions passed red/green; full project build-patch tests 28/28, project typecheck, lint, formatting, and diff check passed. Independent Astra review found no blocker. Incoming PATCH `Form` is treated as the immediately previous Builder's modern form alias; already stored unmarked historical `Form` continues to migrate to WebhookForm. The old patch protocol has no client-release identifier, so pre-October-14 live clients adding new ambiguous IDs cannot be identified separately. - Latest review-fix head `ce05dd733a`: Main workflow [run 39397337356](https://github.com/webstudio-is/webstudio/actions/runs/39397337356) passed, including six E2E shards after a targeted shard-4 rerun for an unrelated MDX slash-menu timeout. Approved visual regression [run 39397395206](https://github.com/webstudio-is/webstudio/actions/runs/39397395206), submodules, and PR lint passed. The unapproved synchronize visual run was cancelled by the approved rerun. |
||
|
|
cdfef44a6a |
fix: Resolve recent content and CSS issues (#6567)
Closes #6554 Closes #6555 Closes #6557 Closes #6559 Closes #6560 Closes #6563 Ref #6558 Ref #6564 ## Change - Preserve `color-mix(... var(...))` in border shorthands, including when width or style is omitted. - Keep bound JSON-LD objects intact, generate valid conditional Content Block JSX, accept truly empty content queries, and reject missing source fields. - Let custom Open Graph values replace defaults and avoid evaluating a Content Block source in the server loader when it has no Resource dependencies. - Add bounded error type and request ID diagnostics to automatic MCP reports. This helps investigate future #6558/#6564 occurrences; it does not claim to fix those unreproduced write failures. The generated API contract is updated. - No persisted-data migration or new user-facing format is needed. ## Verification - New border and missing-source regressions failed before their fixes and pass afterward. Focused package tests, all eight affected package typechecks, lint, generated-API and package-boundary checks pass on the combined branch. - `pnpm fixtures` completed. Its only differences were unrelated remote fixture content (`/acme-test` → `/olegs-test`), excluded from this PR. - Full local Luna/low agent evaluations: 8/9 fixtures passed. `managed-form-runtime-v1` missed its action check, also seen on earlier separate runs. Comparison baselines are incompatible or absent; none were updated. - Required CI is green after the simplification: main workflow (including all Builder E2E shards), visual regression, submodules, and PR lint. ## Checklist - [x] Reproduce the six fixable reports and cover them with regressions. - [x] Investigate #6558/#6564 and add safe diagnostics for future reports. - [x] Address and resolve all review comments on the three original PRs. - [x] Review documentation impact: existing product docs describe the intended behavior; report diagnostics are internal, so no docs update is needed. - [x] Critically review and simplify the combined diff; verify focused tests and package checks. - [x] Simplify recursive content-query evaluation state and verify its regressions. - [x] Required PR CI passes after the simplification. |
||
|
|
fad586889a |
feat: Paste copied variables with keyboard (#6549)
## Summary - Recognize copied variable payloads in the design mode keyboard paste pipeline. - Keep variable clipboard utilities in the shared copy/paste folder so both the settings-panel feature and shared paste handling import from shared code. ## Verification - Oxfmt and Oxlint passed for the changed Builder files. - All CI checks passed on the latest commit, including typecheck, tests, E2E, and visual regression. - Local Vitest and typecheck could not start because this checkout is missing existing dependencies (`email-addresses`, `ipaddr.js`, `undici`, and `tldts`). |
||
|
|
7953162dc3 |
fix: truncate long share link names (#6550)
## Summary - Constrain share link names to the available row width - Show an ellipsis when a name is too long ## Verification - git diff --check |
||
|
|
cb0cc5c125 |
fix: load inspected external resources in Builder (#6548)
## Summary - Keep inspected Resource loads on the same URL + RequestInit fetch path as ordinary loads. The Request object is retained only for exchange inspection. - Fix the Resource editor Refresh action for external APIs without changing Builder's existing public-IP guarded Node transport. - Add a refresh-button test, a deterministic inspected POST test, and opt-in live GET checks for the Holy Redeemer API. ## Tests - `pnpm --filter @webstudio-is/sdk exec vitest run src/resource-loader.test.ts --reporter=dot` (60 passed) - `pnpm --filter @webstudio-is/builder exec vitest run app/builder/features/settings-panel/variable-editors/http-resource-refresh.test.tsx --project core-browser-builder-settings --reporter=verbose` (1 passed) - `WEBSTUDIO_LIVE_RESOURCE_GET=1 pnpm --filter @webstudio-is/builder exec vitest run app/services/safe-resource-fetch.live.server.test.ts --project core-server --reporter=verbose --silent=false` (3 passed; normal and inspected GET returned 200) - SDK and Builder typechecks; `oxlint` and `oxfmt --check` on changed files ## Runtime note The current Vercel Builder still uses its Node public-IP guarded transport. A future Cloudflare Worker transport requires a separate migration with `global_fetch_strictly_public` enabled; this PR does not switch to unrestricted fetch. |
||
|
|
3f18f44456 |
fix: Align Form Action add button with panel headers (#6541)
## Change Settings collapsibles now use `fullWidth` so right-side Builder Style and Settings panel content owns its inline padding. Existing visual insets are preserved, and Action explicitly owns its zero inset so its header and action rows can span the section width. - Forward `fullWidth` through `CollapsibleSectionWithAddButton`. - Remove the `fullWidth` prop and padding wrapper from `StyleSection`; all ten StyleSection callers own their content insets locally, including Size's zero inset and full-width separator. - Move Properties & attributes, diagnostics, individual Style panel section insets into their own content. - Keep normal property controls inset and let Action control its own header, rows, error, and redirect spacing. - Retain the earlier Form Action add-trigger and resource-row changes. Action content starts below its full section header with a small gap. ## Implementation - [x] Prevent overlap between the Action section header and its items: start content below the full header with the existing 2px gap, preserving row size and inset. - [x] Review padding-only wrappers from the right-side Settings and Style panel refactors. Apply padding directly to an existing Flex container where possible and remove redundant Box wrappers. - [x] Replace the Action dropdown hardcoded 180px minimum width with the closest existing spacing token, `theme.spacing[26]` (176px; 4px narrower). - [x] Review documentation impact for the dropdown token change: reviewed the Form action documentation; no update required. - [x] Remove the `fullWidth` prop from `StyleSection`. Each individual Style panel section content container explicitly owns its inline padding: ordinary sections retain 12px and Size retains zero inset, with existing gaps preserved. - [x] Give CollapsibleSection content zero inline padding using the existing `fullWidth` API for right-side Builder Style and Settings panel sections. Each section explicitly owns its inline padding, preserving current visual insets and allowing sections such as Action to control alignment. ## Verification - Action spacing: 13 focused Chromium browser tests, focused oxlint, formatting, and `git diff --check` passed. Geometry asserts a 36px header, 2px gap before the first item, button clearance, and unchanged 24px row height. - Restored Page Settings to its original padding ownership; all 7 focused Page Settings Chromium tests, focused oxlint, and `git diff --check` passed. - Padding-wrapper follow-up: removed redundant Box wrappers around the Navigation mode Flex and baseline Action header fixture; audited right-side Settings and Style panel padding owners. 10 focused Action Chromium tests, focused oxlint, and `git diff --check` passed. - Dropdown token follow-up: 13 focused Action Chromium tests, focused oxlint, and `git diff --check` passed. The browser assertion verifies the 176px computed minimum width. - 428 Style panel Chromium browser tests passed after moving padding into individual sections. - 43 focused Chromium browser tests passed: Action, PropsSection, page settings, search, diagnostics, and request inspector. - Builder TypeScript check, focused oxlint, and `git diff --check` passed. - Assert the Properties collapsible has zero inline padding while its child owns its 12px inset; existing Action geometry checks pass. |
||
|
|
c389cd3bb2 |
fix: classify errors with structured codes and types (#6539)
Invalid or revoked share links now return an actionable 401 response instead of an unexpected server error. Missing project and workspace records receive distinct responses at their lookup sites. Token, project, and workspace lookups use `.maybeSingle()` and rely on database uniqueness. Missing projects have an explicit `ProjectNotFoundError`; the loader no longer interprets `PGRST116`. The API preserves `UNAUTHORIZED` and `INVALID_AUTH_TOKEN` when authorization fails during context creation, so CLI and MCP clients can offer relinking guidance without inspecting error text. Tokens without API access carry `API_ACCESS_DISABLED`. A repository audit replaces error-message matching with typed errors, stable codes, or structured diagnostics in CLI argument/configuration handling, session conflicts, preview cleanup, import retries, domain unpublishing, asset validation, JSX/expression diagnostics, CSS validation, and publish diagnostics. `AGENTS.md` now explicitly prohibits message-based error classification and recovery. The existing build-permission preflight now advertises structured import-error support. Clients upload assets before importing into older servers that do not advertise it; newer servers keep selective retries using structured missing-file details. The internal template-validation error class remains outside public JSX bindings and input descriptions. Shared Builder `parseError` normalizes tRPC and local validation errors, so UI components only add display context instead of decoding transport payloads. Simplification removes three redundant error classes, catch-and-ignore handlers, an unused formatter, and nested loader error handling. Unpublishing an already unpublished domain succeeds after authorization. CLI configuration failures use the existing shared error handling, and plan parsing catches only JSON parsing failures. Validation: affected suites passed (271 tests across 8 suites in the latest simplification checks), affected package typechecks passed, and lint, package-boundary checks, and `git diff --check` passed. The audit replaced synthetic parser branches and private-method assertions with actual tRPC round trips, PostgREST responses, and public VM behavior. Controlled regressions confirmed that removing serialized validation details and exposing the internal JSX error class both fail the revised tests. Astra reviewed the production changes at medium effort; its compatibility finding and the GitHub JSX-binding comment are fixed, and follow-up review found no remaining substantive issues. |
||
|
|
33f7b350bc |
fix: Improve Form setup and email settings guidance (#6536)
## User-facing features and how to test them - Project Settings → Emails → Reply to has an info tooltip explaining how its display name is used in visitor confirmation subjects and messages, and when “Site Owner” is shown. Test it by hovering or focusing the info icon. - When a Form has no resources available for its Action, clicking the Action + button explains that a resource must first be created in Variables. Test it with a Form that has no action-capable resources, then create a resource and verify it can be added to the Action. - Success redirect is shown inside the Action control only when Resource actions are connected. Test by switching between navigation mode and a Form with a Resource action; the redirect field should appear only for Resource actions, and its saved value should return when a Resource action is added again. - The Action control no longer offers Disable/Enable. Connected Resource actions run in their configured order. The Form Action model has no `enabled` field; older saved JSON with that extra key is read as an ordinary connected action. ## Verification - Focused UI test for the Reply to tooltip. - Focused UI test for the empty Action resource tooltip. - Manually verify Success redirect visibility and value retention while switching modes. - Manually verify all connected Actions execute in order, including older saved Actions carrying an ignored `enabled` key. ## Review follow-up - Address all current open code review comments on this PR, verify the fixes, and reply to each addressed thread before resolving it. ## CI repair follow-up - Inspect the latest failing checks on this PR, fix the causes on the existing branch, and verify the affected checks locally and in CI. ## Follow-up audit (2026-10-14) - Audited head `8fb0b44`, all inline review threads and the PR conversation. The earlier Reply to tooltip comment was answered and resolved. Two P1 comments remained: required boolean `enabled` validation in CLI evaluations, and execution of saved disabled actions. - `builder-e2e (shard-6)` failed because Preview returned `Disabled` between `First` and `Last` in the configured failure results test. The failure matches the security review finding. `builder-e2e (shard-2)` was still in progress at audit start; the staging deployment workflow was cancelled. - The follow-up filters saved disabled actions from execution and restores boolean `enabled` validation in both CLI evaluations. Builder labels saved disabled actions. Focused SDK (55), CLI (222), and Builder (45) tests, plus all three affected package typechecks, passed locally. - Pushed `7c2b90e082`; replied to both P1 review threads, then resolved them. Its main CI run passed all 25 test/check jobs, including all six Builder E2E shards and the previously failing shard 6. - A later P2 review identified stale CLI/MCP guidance. Commit `160a9d1707` corrects the source and generated docs; focused docs tests (12) and CLI typecheck pass locally. Replied to and resolved that thread. On this latest head, the main CI run completed successfully: 27 check jobs passed, including all six Builder E2E shards; one visual-regression job was skipped. All three required checks passed. The automatically triggered staging deployment workflow was cancelled under the no-deployment instruction. ## Input type default follow-up - Show `text` as selected in the Props panel for both the HTML `input` element and the Input component when their `type` prop is unset, matching their rendered default. Verify both variants in the Builder and retain explicit type choices. ## Current review-thread follow-up (2026-10-14) - Recheck every unresolved review thread against the current PR head, make and validate any required fixes, and reply with the resolution before closing each thread. Keep this work on the existing PR branch; do not deploy. ## Input type metadata follow-up (2026-10-14) - Move the unset input `type` display default into component/element property metadata for both the Input component and native HTML `input` element. Remove the Props-panel special case, keep the implicit default out of saved props, and cover both variants with regression tests. - Run focused checks, push the fix to this PR, and cancel any automatically triggered staging deployment workflow. Do not deploy manually. ## Props metadata selector simplification follow-up (2026-10-15) - Refactor `$selectedInstancePropsMetas` after the input default metadata change. Preserve the current metadata, ordering, and attribute override behavior while simplifying the selector's structure and abstraction boundaries. - Run focused checks, commit and push to this PR branch. Cancel any automatically triggered staging deployment; do not deploy manually. ## Latest review follow-up (2026-10-15) - Audit the latest PR head, all unresolved inline review threads, and general PR comments. Address actionable feedback on this branch, run focused validation, then reply and resolve each addressed thread. Cancel any automatically triggered staging deployment; do not deploy manually. ## Form Action model decision (2026-10-15) - Remove the Form Action `enabled` field completely from the type, serialization, validation, execution, fixtures, and current docs. Accept older saved JSON containing that extra key by ignoring it, including `enabled: false`; action presence and valid IDs determine execution. - Update the CLI evaluation to assert intended action IDs and order without using `enabled`. Address the latest P2 review finding against this model. Run focused checks, push on this PR branch, and cancel any automatic staging deployment. No manual deployment. ## Action control visual follow-up (2026-10-15) - Match the Form Action plus button to the established Variables section plus button style and implementation where appropriate. Keep the existing icon. - Give the Action resource dropdown a stable width comparable to the Variables resource dropdown, including when available resource names are short. - Add focused UI coverage, validate locally, commit and push to this PR branch, and cancel any automatic staging deployment. Do not deploy manually. ## Action plus hover follow-up (2026-10-15) - Match the Action `+` button's hover and focus styling exactly to the plus buttons in panels such as Variables. Compare their actual implementations, preserve the existing icon, and add focused UI coverage where appropriate. - Run focused checks, commit and push to this PR branch. Cancel and confirm any automatically triggered staging workflow; do not deploy manually. ## Current CI fix (2026-10-15) - Inspect the latest check rollup and failing job logs on the current PR head. Fix all current failures on this branch, run focused local validation, push the changes, and monitor the new head until checks complete. - Cancel and confirm any automatically triggered staging deployment. Do not deploy manually. |
||
|
|
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 |