6658 Commits
Author SHA1 Message Date
Oleg Isonen 631ea07068 Merge branch 'main' into release-31-08-2026.staging 0.296.0 2026-09-01 01:52:49 +01:00
Oleg Isonen c9618de88b fix: Scaffold projects with attribute aliases (#6300)
## Summary

- recover from every standard HTML/SVG and React attribute alias
collision during generated JSX export
- keep the last persisted value for non-class collisions, matching
existing duplicate-prop recovery
- merge distinct class aliases with generated atomic classes
- keep shared collision validation strict in authoring, import, MDX, and
instance-conversion paths

## Implementation

The React SDK generator now normalizes safe instance props before
generating JSX. One map keyed by the final JSX name provides
deterministic last-value recovery for exact duplicates and non-class
aliases. A second map keyed by the original class spelling preserves one
value per class alias so those composable values can be merged into the
single emitted `className` attribute.

Projected resource props use the same normalized-name set, avoiding a
second route to duplicate generated JSX properties.

## Checklist

- [x] Reproduce the reported `class`/`className` failure on unchanged
`main`
- [x] Reproduce non-class failures including `tabindex`/`tabIndex`,
`for`/`htmlFor`, and `readonly`/`readOnly`
- [x] Cover every current standard-to-React alias pair in both
persistence orders
- [x] Preserve exact duplicate-prop last-value behavior
- [x] Merge distinct class aliases with atomic classes
- [x] Keep strict shared validation outside generated JSX recovery
- [x] Review documentation impact; no user-facing documentation changes
are required
- [x] Review fixture impact; no fixture inputs or generated fixture
outputs are affected
- [x] Run targeted tests, typecheck, lint, build, and direct generator
reproducers
- [ ] Run `pnpm evaluations` if explicitly approved

## Verification

- `pnpm --filter @webstudio-is/react-sdk exec vitest run
src/component-generator.test.tsx --pool=forks` — 156 passed
- `pnpm --filter @webstudio-is/content-engine exec vitest run
src/jsx-attributes.test.ts --pool=forks` — 3 passed
- `pnpm --filter @webstudio-is/react-sdk typecheck`
- `pnpm --filter @webstudio-is/react-sdk build`
- `pnpm exec oxlint --deny-warnings
packages/react-sdk/src/component-generator.ts
packages/react-sdk/src/component-generator.test.tsx`
- direct generator reproducers now emit one normalized property for
`tabIndex`, `htmlFor`, and `readOnly`

The exhaustive regression test is derived from the shared attribute
mapping, currently covering 105 non-class pairs in both orders; the
separate class test covers exact duplicate recovery, distinct alias
merging, and atomic classes.

Agent evaluations were not run because they require explicit approval.

Closes #6299
2026-09-01 02:51:57 +02:00
Oleg Isonen 1de752f2ae fix: Keep Slot colors consistent (#6296)
## Summary

- Define the purple treatment as a semantic secondary-accent CSS color
family.
- Use its foreground in the Layers panel and its
background/on-background pair on the canvas.
- Add visual regression coverage that shows the actual Navigator and
canvas Slot treatments together.
- Keep the same purple in light mode and use a more chromatic, readable
foreground in dark mode.

## Implementation

The dark-theme migration replaced the former reusable-instance color
with two unrelated choices: the blue accent foreground in the navigator
and an accent-derived purple canvas fill.

This change adds three generated, theme-aware semantic colors using
Craft's existing naming and explicit foreground pairing conventions:

- `--background-accent-secondary` for Slot canvas labels and outlines.
- `--foreground-accent-secondary` for reusable instances in the
navigator.
- `--foreground-on-accent-secondary` for content on the strong
secondary-accent background.

The secondary accent derives from the theme accent. Its foreground keeps
the existing strong purple in light mode and raises dark-mode lightness
without washing out its chroma, so reusable instances remain visibly
distinct from ordinary Navigator text.

Portable light and dark visual stories render the real `NavigatorTree`
beside the real canvas `Outline` and `Label` Slot variant. This covers
the user-reported cross-surface consistency and the dark-mode foreground
directly.

## Todo

- [x] Trace the regression to the color-system migration.
- [x] Add the secondary-accent semantic CSS family and generated types.
- [x] Add foreground/background contrast contracts across supported
themes.
- [x] Update navigator and canvas consumers.
- [x] Add light and dark visual regression coverage for the actual
Navigator and canvas treatments.
- [x] Confirm the visual comparison fails before approval and inspect
the rendered screenshot.
- [x] Run Design System and focused Builder tests.
- [x] Run Design System and Builder typechecks, scoped lint, and
formatting checks.
- [x] Review fixture impact; no fixture inputs or generated fixture
outputs are affected.
- [x] Review documentation impact; update the internal color-system
reference. Authoritative user documentation is unaffected.

## Verification

- `pnpm --filter @webstudio-is/design-system test` — 223 tests passed.
- `pnpm --filter @webstudio-is/builder exec vitest run --project
core-browser-builder-features
app/builder/features/navigator/navigator-tree.test.ts` — 20 tests
passed.
- The dark secondary-accent regression test was confirmed to fail with
the washed-out foreground, then pass with the stronger chromatic
foreground.
- `pnpm --filter @webstudio-is/design-system typecheck` — passed,
including generated color-type freshness.
- `pnpm --filter @webstudio-is/builder typecheck` — passed.
- Scoped `oxlint --deny-warnings` — passed.
- Scoped `oxfmt --check` — passed.
- `pnpm visual-regression -- --base origin/main --grep
"Builder/Navigator/Navigator tree"` — expected failure with two added
visual stories; both light and dark screenshots were inspected.

The source-contract test was confirmed to fail before the semantic
variables were added, then pass with the implementation. Browser
contrast tests cover both new pairings across light/dark modes and the
complete supported theme parameter range.

Closes #6295
2026-09-01 02:51:13 +02:00
Oleg Isonen 4a10645cb8 docs: Explain Builder themes and CSS warnings (#6298)
## Summary

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

## Implementation

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

## Checklist

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

## Verification

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

## Known limitations

None.

Closes #6297
2026-09-01 01:06:21 +02:00
Oleg Isonen f4eeff0ddf build: Release 31-08-2026 0.295.0 2026-08-31 22:54:13 +01:00
Oleg Isonen 81d3ddf076 fix: tolerate duplicate props during JSX export (#6294)
## Summary

- tolerate duplicate persisted props during JSX generation
- preserve the last value for a duplicate prop name
- add regression coverage for duplicate Image width props

## Verification

- `pnpm --filter @webstudio-is/react-sdk exec vitest run
src/component-generator.test.tsx --pool=forks`
- `pnpm --filter @webstudio-is/content-engine exec vitest run
src/jsx-attributes.test.ts --pool=forks`
- `pnpm --filter @webstudio-is/react-sdk typecheck`
2026-08-31 23:51:40 +02:00
Oleg Isonen 7bec7c0488 fix: Share split button active state (#6290)
Ref #3818

## Summary

- Apply the primary toggle’s pressed surface to both split-button
segments.
- Keep both buttons independently actionable and preserve their shared
hover behavior.

## Implementation

The split-button directly styles the adjacent menu segment when the
primary segment has `aria-pressed`. The menu segment keeps its separate
action and expanded hit target, so Builder state and color logic are not
duplicated.

## Todo

- [x] Share the active surface across both segments.
- [x] Preserve independent actions, disabled behavior, and shared hover
behavior.
- [x] Verify light and dark Storybook rendering.
- [x] Run typecheck and lint.
- [x] Review documentation impact — no documentation changes are
required for this restored component behavior.

## Verification

- `pnpm --filter @webstudio-is/design-system typecheck`
- `pnpm lint`

No fixture inputs or generated fixture outputs are affected.
2026-08-31 23:47:01 +02:00
Oleg Isonen a8c1726418 fix: stabilize CLI bundle contract after publish (#6293)
## Summary

- bundle Zod into the published CLI because `bundleVersion` derives from
Zod schema internals
- bundle `@emotion/hash`, which produces the contract identifier
- prevent compatible dependency updates at install time from changing an
already-published CLI's contract
- add regression coverage for the CLI bundling boundary

## Root cause

`webstudio@0.294.0` externalized `zod` and declared `^4.4.3`. The
Builder was built with the lockfile version, while a fresh `npx
webstudio@latest` installation resolved Zod 4.5.4. Because the project
bundle contract is calculated at runtime from Zod definitions, the
Builder expected `bundle-1mym82p` while the freshly installed CLI sent
`bundle-8e39e9` despite both being the same Webstudio release.

## Packaged CLI reproduction

Tested the CLI built from the parent commit and this branch in the same
isolated `webstudio@0.294.0` installation with Zod 4.5.4. Both builds
executed the real local `webstudio sync` command against the same
capture server.

- Before:
`{"label":"before","zod":"4.5.4","bundleVersion":"bundle-8e39e9"}`
- After:
`{"label":"after","zod":"4.5.4","bundleVersion":"bundle-1mym82p"}`
- Live Builder: `{"bundleVersion":"bundle-1mym82p"}`

The fixed local CLI now sends the exact contract required by the live
Builder even though the install environment still contains Zod 4.5.4.

## Verification

- isolated before/after packaged CLI test through the real `sync`
request path
- `pnpm --filter webstudio exec vitest run
src/cli-bundle-externals.test.ts`
- `pnpm --filter webstudio typecheck`
- `pnpm --filter webstudio build`
- verified emitted CLI files contain no external imports from `zod` or
`@emotion/hash`
- `pnpm exec oxfmt --check packages/cli/vite.config.ts
packages/cli/src/cli-bundle-externals.test.ts`
- `git diff --check`
2026-08-31 23:45:45 +02:00
Oleg Isonen c49c0786be fix: unify dashboard background surface (#6287)
## Description

1. What is this PR about (link the issue and add a short description)

## Steps for reproduction

1. click button
2. expect xyz

## Code Review

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

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
0000)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
2026-08-31 21:55:59 +02:00
Oleg Isonen f953d1be37 fix: Restore the login background gradient (#6286)
## Summary

Restores the original Webstudio login gradient after the color-system
migration added a dark scrim over it. Fixed Webstudio identity artwork
now belongs to the Design System, while generic surfaces, content,
translucency, and overlays use the semantic color system instead of a
Builder-local brand color bag.

## Architecture

- The Design System exports only the fixed Webstudio background
gradient, progress gradient, and progress shadow as brand artwork.
- Generic black, white, translucent panel, and scrim entries were
removed rather than moved.
- Login and error surfaces use semantic light/dark pairings.
- Project-card tags derive their local 30% scrim from the semantic
overlay color.

## Todo

- [x] Restore the original login gradient without a scheme-dependent
scrim.
- [x] Move fixed identity artwork into the Design System package.
- [x] Remove the Builder-local brand color registry.
- [x] Replace generic fixed colors with semantic colors or local
derivations.
- [x] Add explicit dark-mode Storybook coverage for the login screen.
- [x] Run Design System and Builder typechecks.
- [x] Run Design System tests, scoped lint, formatting, and
package-boundary validation.
- [x] Review documentation impact and document ownership in the Design
System color README.

## Verification

- Design System tests: 222 passed.
- Design System and Builder typechecks: passed.
- Scoped Oxlint and formatting: passed.
- Package-boundary validation: passed.
- Focused visual regression: 23 affected stories compared; the newly
added dark login story is the only difference.
- Fixture impact: no fixture inputs or generated outputs are affected.

Ref #3818
2026-08-31 21:16:26 +02:00
Oleg Isonen 4273b7001f refactor: Organize Builder development and test tooling (#6285)
## Outcome

Builder development, backend, E2E, and CI tooling now share one
ownership model: Builder defines named commands and backend modes, while
CI only schedules those commands.

## Technical choices

- Group shared Compose files, PostgreSQL initialization, and backend
helpers under `apps/builder/backend`.
- Resolve `compose.<mode>.yaml` by convention instead of maintaining
Compose filename lists in scripts and workflows.
- Keep persistent development and disposable test Compose projects
separate by default, while allowing the standard `COMPOSE_PROJECT_NAME`
override.
- Make schema snapshots invalidate from discovered migration sources and
backend files.
- Move E2E behavior recipes into named Builder commands; retain concise
root proxies for developer entry points.
- Let Vitest allocate available browser ports instead of maintaining a
cross-package port registry.
- Keep test-suite scheduling explicit in `main.yml`, but move executable
test commands to root package scripts.
- Move fixture-size enforcement from inline GitHub JavaScript into a
locally runnable repository script.

## Todo

- [x] Consolidate Builder backend and Compose ownership.
- [x] Add named development, E2E, schema-cache, Supabase, and CI-shard
commands.
- [x] Remove Compose topology and behavior flags from CI.
- [x] Remove the static Vitest browser-port registry.
- [x] Extract fixture-size checking from the workflow.
- [x] Verify migration bootstrap, snapshot-backed E2E, tests, builds,
types, formatting, lint, generated contracts, and workflow syntax.
- [x] Review documentation impact: updated the E2E developer guide; no
user-facing `docs/` content is affected.

## Verification

- Full package, CLI, Builder core, and Builder app test suites
- Full workspace typecheck
- Builder production build
- Schema-cache generation through the complete migration history
- Supabase migration compatibility
- Snapshot-backed Playwright animation workflow
- Backend regression tests, including demonstrated fail/pass coverage
- All four Compose mode configurations
- `oxfmt`, `oxlint`, package boundaries, generated API, and generated
docs checks
- `actionlint` and Bash syntax checks

The fixture-size checker preserves the existing Linux `du` thresholds.
Its three inputs build locally; macOS allocation sizes are not directly
comparable to the Linux CI thresholds, so CI remains the authoritative
size result.
2026-08-31 21:09:54 +02:00
Oleg Isonen bc8630f413 fix: Gate Builder releases on the published CLI contract (#6282)
Closes #6280

## Summary

- Expose the deployed Builder's CLI-facing bundle contract through a
non-cached, read-only endpoint.
- Resolve and execute the exact `webstudio@latest` registry artifact,
observe the bundle version sent by its real `sync` request, and require
an exact match before assigning the release staging alias.
- Include expected and received bundle versions in runtime and
release-gate errors.

## Technical choices

- The existing generated `bundleVersion` remains the single
compatibility boundary. Builder-only changes outside that project-bundle
contract do not require a CLI release.
- The CLI sync probe runs against a local fixture API and temporary
directory. It reads the deployed runtime contract but never reads or
modifies a Cloud project.
- Verification runs after Vercel creates the exact production-built
deployment and before the release staging alias is assigned. A mismatch
therefore leaves production domains untouched.
- Registry resolution requires version and integrity metadata, then
executes the resolved immutable version rather than source from the
checkout.
- The gate requires the deployed Builder origin and cannot fall back to
the checkout's contract.

## Todo

1. [x] Add the deployed Builder contract endpoint.
2. [x] Probe the exact published CLI through its real sync request.
3. [x] Gate the release staging alias on an exact contract match.
4. [x] Report expected and received versions for compatibility failures.
5. [x] Add regression tests and verify the production Builder build.
6. [x] Review documentation and fixture impact.

## Verification

- `pnpm --filter @webstudio-is/builder exec vitest run
app/services/build-router.server.test.ts
app/services/cli-compatibility-route.node.test.ts` — 12 passed.
- `pnpm --filter webstudio exec vitest run
scripts/builder-contract-gate.test.ts
scripts/release-smoke-registry.test.ts` — 7 passed.
- Rebased onto `main` at `9ec4c820e1`.
- Builder and CLI typechecks passed.
- `pnpm lint` and changed-file formatting passed.
- `pnpm --filter @webstudio-is/builder build` passed and emitted
`/rest/cli-compatibility`.
- The changed GitHub Action parses as valid YAML.
- Real registry probe passed for `webstudio@0.293.0` against
`bundle-kzb45v`.
- Real registry probe correctly blocked current Builder contract
`bundle-1mym82p` and reported expected `bundle-1mym82p`, received
`bundle-kzb45v`.
- Missing staged Builder origin fails closed before registry execution.
- GitHub CI passed all four workflows, including all six Builder E2E
shards.
- The formerly failing empty-MDX insertion workflow passed 8 repeated
runs with 4 workers locally, then passed in Builder E2E shard 4 in CI.
- Full browser-based release smoke was not run, per the request not to
use Chrome. Agent evaluations were not run because no evaluation
approval was requested or received.
- Documentation: no user-facing documentation changes are required.
- Fixtures/generated outputs: no fixture inputs or generated outputs are
affected.

## Operational boundary

The check needs the exact deployed runtime, so Vercel creates an
unaliased `--skip-domain` deployment before verification. A failed check
cannot affect production domains or the normal release alias. Repository
automation cannot prevent a Vercel administrator from manually promoting
an arbitrary deployment outside this release path.
2026-08-31 20:43:52 +02:00
Oleg Isonen 9ec4c820e1 fix: Use writable npm cache for previews (#6272)
## Summary

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

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

## Technical choices

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

## Todo

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

## Verification

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

Closes #6269

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

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

Ref #6182

## Implementation notes

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

## Checklist

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

## Verification

- Project Build: 208 affected tests passed.
- Content Engine Asset session: 8 tests passed.
- CLI documentation tests: 14 tests passed.
- MCP docs generator: 3 tests passed.
- Content Engine, Project Build, CLI, and MCP docs typechecks passed.
- `pnpm oxfmt --check` passed.
- `git diff --check` passed.
- Low-reasoning MCP evaluation passed all documented workflows,
including a real two-client revision conflict, reload, reapply, and
final saved inspection.
2026-08-31 18:39:05 +02:00
Oleg Isonen 8718feb465 test: simplify browser testing infrastructure (#6271)
## Summary

Unifies frontend testing around three intentional boundaries:

- Vitest Node projects for pure and server tests
- Vitest Browser Mode with Playwright/Chromium for DOM and component
tests
- Playwright Test for Builder end-to-end workflows

The Builder no longer uses jsdom. Ordinary E2E workflows use an
automatically managed isolated `page` fixture; explicit contexts remain
only for simultaneous identities and suite-scoped setup.

## Technical choices

- Split Builder tests into one Node project and independent browser
projects so each Chromium/Vite process has a bounded workload.
- Added shared Browser Mode configuration for the affected SDK React
packages.
- Preserve the Builder's production top-window bridge ownership; Browser
Mode uses explicit test-only installation seams.
- Centralized Playwright context options and kept action/navigation
timeouts bounded.
- Validate Playwright filters before Docker, migrations, browser
installation, or Builder builds start.
- Preserve the first failed E2E attempt's report, traces, screenshots,
and videos when CI retries a shard.
- Split static checks, typechecks, CLI tests, core package tests,
browser package tests, and Builder projects into parallel CI jobs with
five-minute hard ceilings.
- Distribute Builder E2E tests across ten isolated shards with two
workers; parallel suites create unique per-worker setup data.
- Reuse color contrast DOM/canvas samples so the exhaustive theme sweep
takes about four seconds instead of timing out at 30 seconds.
- The removed cost is jsdom behavior drift, oversized serial check jobs,
and unnecessary E2E startup for empty selections. Real browser startup
and E2E environment setup remain.

## Todo

- [x] Split Builder pure/server tests from browser/component tests.
- [x] Migrate Builder DOM tests from jsdom to Vitest Browser Mode.
- [x] Migrate affected SDK React DOM tests to the shared Browser Mode
setup.
- [x] Remove jsdom annotations, dependencies, and compatibility setup.
- [x] Replace ordinary manual E2E contexts with the isolated `page`
fixture.
- [x] Keep explicit contexts only for concurrent identities and
`beforeAll` setup.
- [x] Preserve setup authentication isolation from workflow pages.
- [x] Add bounded Playwright action and navigation timeouts.
- [x] Preserve first-attempt E2E failure evidence across CI retry.
- [x] Reject empty E2E selections before expensive environment setup.
- [x] Parallelize repository checks into bounded CI shards.
- [x] Isolate parallel E2E setup data across workers.
- [x] Remove the color contrast sweep bottleneck.
- [x] Update E2E contributor documentation.
- [x] Review product documentation impact; no user-facing product docs
are affected.
- [x] Run the full repository tests and typechecks.
- [x] Run formatting, lint, package-boundary, generated-file, and
fixture checks.
- [x] Complete final correctness and simplification review.

## Verification

- `pnpm --filter @webstudio-is/builder test`
- `pnpm -r typecheck`
- `pnpm lint`
- `pnpm check:package-boundaries`
- `pnpm check:generated-api`
- `pnpm check:generated-docs`
- `pnpm fixtures`
- Design-system: 222 tests pass locally.
- Builder: all seven Vitest projects pass locally.
- The formerly slow E2E shard passes locally in 1m55s.
- Full database-backed Builder E2E execution passes in CI.
- All CI jobs pass; the slowest completed in 4m58s.
- No fixture inputs/outputs or generated files changed.

Ref #6270
2026-08-31 17:17:28 +02:00
Oleg Isonen f6c524572d feat: add Builder dark theme and rebuild colors
## Summary

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

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

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

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

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

## Scope

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

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

## Architecture decisions

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

## Todo

### Color-system foundation

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

### Design-system migration

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

### Inventory and contract

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

### Theme infrastructure

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

### Generic interface migration

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

### Style panel and style-source recipes

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

### Canvas and editors

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

### Explicit color domains

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

### Stories, tests, and legacy removal

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

### Browser-test infrastructure

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

### Verification and documentation

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

## Current verification

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

Ref #3818
Closes #6267
2026-08-30 03:24:56 +01:00
Oleg Isonen e25b7871c3 fix: close navigation overlays for anchor links (#6244)
## Summary

- Close Popover, Navigation Menu, Dialog, and Sheet content when an
ordinary same-context link is activated.
- Resolve nested text and icon clicks through the containing anchor.
- Keep modified clicks, downloads, empty fragments, new browsing
contexts, and Builder canvas interactions unchanged.
- Preserve Dialog focus handling, controlled Navigation Menu behavior,
and existing Navigation Menu Link dismissal.

## Technical choices

- Classify link activation once in the Radix package and share it across
overlay components.
- Handle activation on each content component so portalled content
closes its owning root without a global window listener.
- Close overlays based on link activation rather than route lifecycle,
making hash-only, current-page hash, and cross-page links consistent.
- Preserve Navigation Menu's controlled `value` contract while using
`defaultValue` only to initialize uncontrolled state.
- Resolve `_parent`, `_top`, and named targets against the anchor
document's current browsing context.
- Do not prevent the link's default behavior; native hash scrolling and
framework routing remain responsible for navigation.

## Compatibility

- No schema, migration, data, or public prop changes.
- Existing controlled and uncontrolled Navigation Menu behavior remains
intact.
- Existing Navigation Menu Link behavior remains intact.
- Generated fixture inputs and outputs are unaffected.

## Checklist

- [x] Add shared same-context link activation rules.
- [x] Apply dismissal to Popover, Navigation Menu, Dialog, and Sheet
runtime components.
- [x] Preserve Dialog focus restoration behavior.
- [x] Preserve controlled and uncontrolled Navigation Menu semantics.
- [x] Cover top-level and child browsing-context targets.
- [x] Assert native fragment navigation remains active.
- [x] Add component regression coverage and demonstrate fail/pass
behavior.
- [x] Run targeted lint, tests, type checking, declaration generation,
and package build.
- [x] Review documentation impact; no product documentation changes are
required because this restores expected navigation behavior without
changing the authoring interface.

## Verification

- `pnpm --filter @webstudio-is/sdk-components-react-radix test` — 15
tests passed.
- Regression demonstrations:
  - canceling default activation failed the hash assertion;
  - restoring Dialog trigger focus failed the focus assertion;
- treating all non-`_self` targets as external failed the top-level
`_parent` and `_top` assertions;
- replacing the controlled value with local state failed the controlled
Navigation Menu assertion.
- `pnpm --filter @webstudio-is/sdk-components-react-radix typecheck` —
passed.
- `pnpm --filter @webstudio-is/sdk-components-react-radix dts` — passed.
- `pnpm --filter @webstudio-is/sdk-components-react-radix build` —
passed.
- Targeted `oxlint --deny-warnings` — passed.
- `git diff --check` — passed.

## Known limitations

- Browser layout scrolling is not covered by a published-site E2E.
Component tests verify the native fragment update, overlay state, and
Dialog focus outcome directly.

Closes #6242
2026-08-30 01:35:27 +02:00
Oleg Isonen f9681dc30f feat: MDX-backed Content Blocks (#6234)
## Summary

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

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

Ref #6124
Ref #6182
Closes #6261

## Locked behavior

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

## Reuse strategy

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


## Old-branch porting map — do not rewrite

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

### Already ported

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

### Port with their existing tests

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

### Explicitly do not port

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

## Implementation checklist

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

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

### 2. Shared Markdown and MDX conversion

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

### 3. Invalid and unsafe MDX

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

### 4. File formats and Asset creation

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

### 5. Source contracts and lifecycle

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

### 6. Asynchronous persistence and conflicts

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

### 7. Content mode parity

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

### 8. Template names and permissions

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

### 9. Scope, frontmatter, and diagnostics

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

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

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

### 11. Dynamic sources and publication

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

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

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

### 13. Verification and delivery

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

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

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

### 15. Final architecture review fixes

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


### 16. Final persistence and transfer hardening

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

## Current rebuild status

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

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

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

### 18. Source property UX

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


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

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

#### A. Remove structured frontmatter UI

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

#### B. Preserve source and separate validation

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

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

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

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

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

#### E. Dynamic and shared sources

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

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

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

#### G. Verification and documentation

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

### 20. Content-mode component properties

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


### 21. Designed document shell and Body outlet

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


### 22. Final nested references and frontmatter editing

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

### Before merge

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


### 23. Explicit writable expression bindings

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

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

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

## Todo

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

## Verification

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

Closes #6262
Closes #6263
2026-08-29 18:54:05 +02:00
Oleg Isonen 88ecbc5f7b chore: renew wstd.dev certificate (#6256)
## Summary

Renew the local Builder HTTPS certificate for `wstd.dev` and its
wildcard subdomains.

## Todo

- [x] Replace the tracked certificate, private key, and HAProxy bundle
- [x] Verify certificate dates, SANs, and matching public/private keys
- [x] Verify repository formatting and diff integrity
- [x] Review documentation impact — no product documentation changes are
required

## Verification

- `openssl verify -partial_chain -CAfile https/fullchain.pem
https/fullchain.pem`
- Certificate and private-key public keys match
- HAProxy certificate and bundled private-key public keys match
- `pnpm oxfmt --check` (using the existing Builder dependency
installation)
- `git diff --check`

No fixture inputs or generated outputs are affected.
2026-08-28 11:55:30 +02:00
Oleg Isonen cbe4950cd0 fix: Use available package manager for previews (#6251)
## Summary

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

## Technical choices

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

## Todo

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

## Verification

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

Closes #6250
2026-08-28 00:23:01 +02:00
Oleg Isonen fc6b5f2fde fix: Retry bulk style deletions after conflicts (#6255)
Closes #6254

## Summary

- Mark `styles.deleteDeclarations` as safe to retry after a confirmed
version conflict.
- Recompute the deletion against refreshed style namespaces before the
retry.
- Add a regression covering 185 CSS custom-property declarations and the
complete conflict/refresh/retry path.
- Regenerate the public runtime operation contract.

## Root cause and behavior

`delete-styles` already accepts and plans a 185-declaration payload. The
failure occurs when the remote build changes between the session
snapshot and the commit: the session refreshes, but the operation
contract currently has `retryOnConflict: false`, so the atomic deletion
returns uncommitted and the project version does not advance.

Style declaration deletion is retry-safe because the operation is
recomputed from the refreshed snapshot and removes only exact
declaration keys that still exist.

The released CLI already writes structured success and failure JSON to
stdout; stderr contains lifecycle diagnostics. No stderr parsing change
is needed in Webstudio. The issue's stderr acceptance criterion was
LLM-authored without captured failure diagnostics in CLI 0.293.0.

## Tradeoffs and compatibility

- Retries only confirmed version conflicts.
- Ambiguous commit results are still not replayed.
- No schema, migration, or user-facing API shape changes.

## Checklist

- [x] Reproduce the 185-declaration CSS-variable deletion through the
CLI/MCP dry-run path.
- [x] Reproduce the unchanged-version failure with a conflicting commit.
- [x] Verify the regression fails before the contract change and passes
afterward.
- [x] Enable conflict retry for `styles.deleteDeclarations`.
- [x] Regenerate runtime operation contracts.
- [x] Review documentation impact; no documentation update is required
for this reliability fix.
- [x] Verify fixture impact; no fixture inputs or outputs are affected.

## Verification

- `pnpm --filter @webstudio-is/project-build test` — 2,132 tests passed.
- `pnpm --filter @webstudio-is/project-build typecheck` — passed.
- Targeted oxlint on changed source/tests — passed.
- `pnpm check:generated-api` — passed; generated files are clean.
- Agent evaluations were not run because they require separate explicit
approval.
2026-08-27 23:50:15 +02:00
Oleg Isonen 981f45fd0d docs: Optimize documentation image sizes (#6253)
## Summary

Replace ten oversized PNG screenshots in the product documentation with
high-quality WebP assets and update their references.

The affected assets shrink from 27,813,051 bytes to 2,822,010 bytes (90%
smaller) while retaining their original pixel dimensions.

## Technical choices

- Encoded screenshots as WebP at quality 88 with sharp YUV conversion.
- Kept original dimensions to preserve UI text legibility and zoom
detail.
- Left animated GIFs unchanged: GIF-native optimization, palette
reduction, and resizing tests increased their file sizes, so changing
them would add cost or reduce clarity.
- Kept documentation content, navigation, and public page paths
unchanged.

## Checklist

1. [x] Audit raster assets in `docs/.gitbook/assets`.
2. [x] Optimize referenced PNG assets above 1 MB where conversion
materially reduces size.
3. [x] Update every affected documentation reference.
4. [x] Verify replacement dimensions and image decoding.
5. [x] Verify visual quality (SSIM 0.961–0.997 plus manual spot checks).
6. [x] Verify new references resolve and removed filenames are no longer
referenced.
7. [x] Review documentation impact; this PR changes documentation assets
and their direct references only.
8. [x] Verify no fixture inputs or generated fixture outputs are
affected.

## Verification

- `git diff --check`
- Reference checks for all ten added and removed filenames
- Pixel-dimension comparison for all ten replacements
- FFmpeg SSIM comparison for all ten replacements
- Manual visual review of representative full-resolution screenshots

The repository formatter excludes these documentation files, so it
reported no matching targets. Agent evaluations were not run because
this is a documentation-asset-only change.

Closes #6252
2026-08-27 17:39:10 +02:00
Oleg Isonen 471f504087 feat: Select unused assets before deleting (#6245)
Closes #6243

## Summary

- Add checked-by-default controls for every asset in the **Delete unused
assets** dialog.
- Add a footer **Select all** control that reflects the complete
selection and toggles all assets.
- Delete only selected assets that remain unused and disable deletion
when the selection is empty.
- Keep the Assets documentation aligned with the selective deletion
workflow.

## Implementation

The dialog owns a set of selected asset IDs initialized from the
unused-assets result. Individual checkboxes update that set, while
**Select all** replaces it with either every currently unused ID or an
empty set.

Asset usage inputs remain subscribed while the dialog is open. The
deletion payload is intersected with the current unused-assets list, and
this cleanup path does not force deletion. If an asset becomes
referenced before the mutation executes, the runtime rejects its
deletion instead of removing a now-used asset.

## Todo

- [x] Add per-asset selection controls.
- [x] Add and position the **Select all** footer control.
- [x] Limit deletion to selected asset IDs that remain unused.
- [x] Protect against assets becoming referenced while the dialog is
open.
- [x] Add regression tests for individual selection, select-all
behavior, empty-selection protection, selective deletion, and live usage
changes.
- [x] Review documentation impact and update the Assets guides.
- [x] Verify formatting, lint, types, and Asset Manager/assets tests.

## Verification

- `pnpm --filter @webstudio-is/builder exec vitest run
app/builder/shared/asset-manager app/builder/shared/assets` — 26 files,
221 tests passed.
- `pnpm --filter @webstudio-is/builder typecheck` — passed.
- `pnpm exec oxlint --deny-warnings
apps/builder/app/builder/shared/assets/delete-assets.ts
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.tsx
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.test.tsx`
— passed.
- `pnpm exec oxfmt --check
apps/builder/app/builder/shared/assets/delete-assets.ts
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.tsx
apps/builder/app/builder/shared/asset-manager/delete-unused-assets.test.tsx`
— passed.
- Both regression tests were confirmed failing before their respective
implementations and passing afterward.

## Compatibility and follow-ups

No data migrations, public API changes, fixture changes, or known
follow-ups.
2026-08-26 12:14:06 +02:00
Oleg Isonen 9a4ee57929 fix: Create page templates without explicit titles (#6240)
## Summary

Allow `create-page-template` to omit `title` by deriving a fixed title
expression from the required template name. Explicit titles remain
unchanged.

Closes #6239

## Todo

- [x] Reproduce the name-only mutation failure against the persisted
page-template schema
- [x] Derive the omitted title from the template name
- [x] Add and verify a fail-before/pass-after regression test
- [x] Review fixture impact; no fixture inputs or generated outputs are
affected
- [x] Review documentation impact; the existing command schema and
example already document the optional title, so no documentation update
is needed

## Verification

- `pnpm --filter @webstudio-is/project-build test --
src/runtime/page-copy.test.tsx`
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm exec oxfmt --check
packages/project-build/src/runtime/page-copy.ts
packages/project-build/src/runtime/page-copy.test.tsx`
- `pnpm exec oxlint --deny-warnings
packages/project-build/src/runtime/page-copy.ts
packages/project-build/src/runtime/page-copy.test.tsx`
- Full package test run: 2,127 passed; three unrelated existing
descendant-selector failures remain in `src/runtime/styles.test.ts`
- Agent evaluations were not run because they require separate approval
2026-08-26 11:01:29 +02:00
Oleg Isonen db2175f333 fix: transfer pasted assets across deployments (#6228)
Closes #6227

## What changed

Cross-deployment copy/paste now transfers referenced assets into the
destination project before inserting instances, pages, templates, or
folders.

- Add optional source deployment metadata to the existing v0.1 clipboard
formats.
- Keep old v0.1 payloads readable and keep new payloads readable by
older builders, which ignore the additive field.
- Reuse the existing asset upload-by-URL flow for images, fonts, videos,
and files.
- Preserve source video dimensions through the existing upload route.
- Replace source asset IDs and font-family references with destination
values before runtime insertion.
- Include and remap page social images and marketplace thumbnails.
- Reject current clipboard payloads whose page metadata references
omitted assets.
- Leave the public `pageTransfer.insert` runtime input unchanged.
- Abort insertion with a paste error when an asset cannot be imported.

No clipboard version increase, custom network fetcher, address
blocklist, rollback framework, or global paste wrapper is introduced. If
a multi-asset import partially uploads before a later upload fails, the
completed uploads may remain unused.

## Checklist

- [x] Rebased onto `main`
- [x] Preserve the existing v0.1 clipboard format
- [x] Transfer and remap instance assets
- [x] Transfer and remap page, template, and folder assets
- [x] Preserve video metadata
- [x] Keep runtime page-transfer API compatible
- [x] Add regression coverage
- [x] Review documentation impact and update page-copy documentation
- [x] Run Builder tests and typecheck
- [x] Run project-build tests and typecheck
- [x] Run lint, package-boundary, and generated-API checks

## Verification

- Builder full suite: 219 files, 2,552 tests passed
- Project-build full suite: 78 files, 2,129 tests passed
- Final affected suite: 84 tests passed
- New regressions were confirmed failing before the fixes and passing
afterward
- Builder and project-build typechecks passed
- `pnpm lint` passed
- `pnpm check:package-boundaries` passed
- `pnpm check:generated-api` passed
- Fixture inputs and generated fixture bundles are unaffected
2026-08-25 16:44:38 +02:00
Oleg Isonen 61df069016 fix: Report recovered project settings commits (#6236)
## Summary

A stale project-settings mutation could retry and commit successfully
while retaining the original `CONFLICT` error diagnostic. CLI and MCP
then reported failure even though the requested settings were stored and
the project version advanced.

Successful conflict retries now retain only recovery information. Failed
retries still preserve their errors.

## Implementation checklist

- [x] Reproduce the stale project-settings conflict before changing the
implementation.
- [x] Remove the obsolete error diagnostic only after a retry succeeds.
- [x] Cover `update-project-settings` commit status, resulting version,
and remote read-back.
- [x] Confirm the regression fails before the fix and passes after it.
- [x] Review documentation impact; no user documentation changes are
required.
- [x] Review generated contracts and fixtures; neither is affected.
- [ ] Confirm CI passes.

## Verification

- ProjectSession suite: 49 tests passed.
- Complete Project Build suite: 2,126 tests passed.
- Project Build typecheck passed.
- CLI ProjectSession adapter suite: 8 tests passed.
- Changed-file lint and formatting passed.
- Agent evaluations were not run because they require separate approval.

Closes #6235
2026-08-25 15:03:50 +02:00
Oleg Isonen 130d371759 perf: Skip resources in statically hidden subtrees (#6233)
## Summary

- Skip read-time resources scoped to an instance with literal
`show=false` or any descendant of that instance.
- Use the same static reachability rule in the Builder client preloader
and CLI-generated published resource map.
- Keep dynamic `show` expressions on the existing loading path because
their result may depend on resource data.

## Data path and performance

In the Builder, the client-side resource request set now excludes
statically hidden subtrees before requests enter the preloader.

For generated sites, the CLI still scans project instances and props
during build, but removes statically hidden subtrees from the page build
data. Their read resources therefore never enter the generated
server-side resource map and no corresponding resource network request
runs during page rendering.

This removes request and server/network cost for literal-`false`
subtrees. It does not remove build-time project scanning, and resources
under dynamic conditions still load.

## Compatibility

- No project schema or persisted data changes.
- No migrations.
- Action-bound resources retain their existing explicit-trigger
behavior.
- Dynamic `show` expressions retain current behavior.

## Checklist

- [x] Add shared static-subtree reachability logic.
- [x] Apply it to Builder preview resource preloading.
- [x] Apply it to generated/published resource collection.
- [x] Add Builder and generated-output regressions for direct,
descendant, visible, and dynamic cases.
- [x] Demonstrate the new regressions fail against the previous behavior
and pass with the implementation.
- [x] Review authoritative `docs/` content; no documentation update is
required because the UI and authoring workflow are unchanged.
- [x] Review fixture inputs and outputs; no fixture contains this
resource/show combination, so fixture regeneration is not required.

## Verification

- `pnpm --filter @webstudio-is/sdk test`
- `pnpm --filter @webstudio-is/react-sdk test`
- `pnpm --filter @webstudio-is/builder exec vitest run
app/shared/nano-states/props.test.tsx`
- `pnpm --filter webstudio build:content-runtime`
- `pnpm --filter webstudio exec vitest run src/prebuild.test.ts`
- Typechecks for SDK, React SDK, Builder, and CLI
- Targeted `oxlint --deny-warnings`
- `git diff --check`

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

Closes #6232
2026-08-24 18:45:46 +02:00
Oleg Isonen 8e3f8e3234 fix: Avoid Builder E2E registry throttling (#6231)
## Summary

- pull the pinned Supabase Postgres image from its official GHCR package
instead of anonymous Public ECR
- pin the multi-platform index digest shared by both registries

## Technical choice

The existing Public ECR and GHCR tags resolve to the same multi-platform
index digest, so changing registries preserves the amd64 and arm64 image
contents. GHCR public pulls require no long-lived CI credentials.

## Checklist

- [x] Verify Public ECR and GHCR resolve to the same image digest
- [x] Switch the Compose database image to the pinned GHCR digest
- [x] Validate shell syntax and rendered Compose configuration
- [x] Review documentation impact; no user-facing documentation is
affected
- [x] Review fixture impact; no fixture inputs or outputs are affected

## Verification

- `bash -n apps/builder/dev/backend.sh apps/builder/e2e/run-local.sh`
- `docker compose ... config --images`
- `docker buildx imagetools inspect` digest comparison for both
registries
- `git diff --check`

The full Builder E2E suite was not run locally because the change only
selects an identical image from a different registry.

Closes #6230
2026-08-24 16:07:21 +02:00
Oleg Isonen 9e504037fb fix: improve MCP reliability across project workflows (#6222)
## Summary

Fixes the reproducible MCP, preview, redirect, audit, mutation, and
asset-upload failures from the latest issue batch. Historical and
non-reproducible reports were investigated without speculative fixes.
Visual verification is now opt-in unless the user explicitly requests
it.

## Issue checklist

- [x] #6207 — report tested recovery when an explicitly selected browser
fails to start.
- [x] #6208 — accept generic and registered buttons as valid label
targets without requiring a separate form label.
- [x] #6209 — reject unknown style coordinates such as `breakpointId`
and document the supported `breakpoint` field.
- [x] #6210 — traced Node 18 access to CLI 0.293.0 predating the
launcher guard; closed as already fixed by #6201.
- [x] #6211 — verified the supported launcher blocks the reported Node
18 crypto path; closed as already fixed by #6201.
- [x] #6212 — give sequential responsive captures the sum of their
per-capture timeout budgets.
- [x] #6213 — keep preview install, build, and server subprocesses on
the parent CLI Node runtime.
- [x] #6214 — add safe confirmation and diagnostics for ambiguous
mutation responses; this does not claim to remove upstream latency.
- [x] #6215 — confirmed as the same historical Node 18 release path as
#6210; closed as already fixed by #6201.
- [x] #6216 — verified malformed input returns structured errors on
current main; closed as not reproducible and improved future failure
diagnostics.
- [x] #6217 — add transaction-id confirmation for large fragment
mutations after ambiguous 5xx responses; this does not make the
operation asynchronous.
- [x] #6218 — verified the existing DevTools reconnect fix from #6205;
closed as already fixed.
- [x] #6219 — preserve the incoming pathname and query when a redirect
target supplies only a hash.
- [x] #6220 — return ordered `uploaded`, `failed`, and `ambiguous`
results so partial asset successes are not lost or retried unsafely.
- [x] #6221 — verified parser recovery without a Node crash; closed as
not reproducible and improved future JSX failure diagnostics.
- [x] #6223 — verified browser-session reuse, reconnect, and cleanup;
closed as already fixed by #6205 after the reported CLI version.
- [x] #6224 — expose both text-asset sources and encode the exactly-one
requirement in the standard MCP schema.
- [x] #6225 — could not reproduce the project-specific upstream timeout;
closed with safe confirmation coverage and improved structured
diagnostics, without claiming a latency fix.
- [x] #6226 — could not reproduce the project-specific upstream timeout;
closed with safe confirmation coverage for insert-fragment, without
adding a speculative fragment-specific fix.
- [x] #6229 — reproduced the reported two-process sequence with the
current local CLI and a real browser; both captures passed, so the
0.293.0 report was closed without a speculative fix.
- [x] Require explicit user consent before preview, screenshots, diffs,
OCR, or rendered visual audits unless visual verification was requested.

## Technical notes

- Ambiguous mutation responses are confirmed only with the same
idempotent transaction id. If confirmation is also ambiguous, the
session reports `AMBIGUOUS_COMMIT`, refreshes state, and never replays a
fresh mutation.
- CLI error mapping retains the original remote error as a
non-enumerable cause, so JSON gateway status reaches the ambiguity
classifier without exposing raw remote context in serialized
diagnostics.
- Forced asset uploads are not automatically retried after an
unknown/network/5xx outcome. Batch results retain the original input
index, including duplicate filenames.
- Stable issue codes and constraints are retained for diagnostics.
Arbitrary validation paths, messages, argument values, project content,
and raw contextual data are excluded.
- The MCP handshake remains the source of tool input schemas;
goal-specific guides avoid duplicating those contracts and reduce
model-context cost.
- `update-asset-content` now exposes `path` and `content` with a
machine-readable `oneOf` constraint, matching the runtime rule that
exactly one source is required.
- Redirect targets with their own pathname or query still replace those
request components.
- Generated runtime contracts, CLI documentation, product documentation,
and deployment fixtures were regenerated from their sources.

## Verification

- [x] Three independent review/test agents exercised visual consent and
preview behavior; mutation, asset, and privacy behavior; and
accessibility, styles, and redirects.
- [x] `pnpm checks` passed after the final transport follow-up: all
workspace tests and typechecks, lint with warnings denied, package
boundaries, generated API contracts, generated docs and documentation
checks, and the complete fixture link/sync/build pipeline.
- [x] Changed-package suites passed, including Project Build (2,125
tests), HTTP client (42 tests), CLI, Protocol, and Builder.
- [x] Complete `pnpm evaluations` run on the preceding implementation
commit: all five real-world fixtures and every behavioral check passed.
Four baseline comparisons passed; one font-assets sample exceeded only
its model-output token allowance, and its isolated rerun passed.
Evaluations were not rerun after the final transport and schema
follow-ups because fresh approval is required.
- [x] Design evaluation MCP response bytes improved from the 39.8 KB
baseline to 37.6 KB after removing duplicated guide contracts.
- [x] Documentation impact reviewed; generated MCP docs and multi-file
upload guidance are updated.

## Related issues

Closes #6207

Closes #6208

Closes #6209

Ref #6210

Ref #6211

Closes #6212

Closes #6213

Ref #6214

Ref #6215

Ref #6216

Ref #6217

Ref #6218

Closes #6219

Closes #6220

Ref #6221

Ref #6223

Closes #6224

Ref #6225

Ref #6226

Ref #6229
2026-08-24 16:07:00 +02:00
Oleg Isonen 626b5e741b fix: Reduce discovery for authored fragments (#6203)
Ref #6177
Ref #6154

## Outcome

Bounded authored-fragment requests no longer receive broad
design/authentication context bundles, registry discovery, or
design-token operations through generic keyword matching.

The initial ProjectSession synchronization is unchanged. Focused target
reads, missing nested schemas, and expression guidance can still add
calls when the requested fragment requires them.

## Root cause

`meta.guide` treated the generic word `design` as visual intent and
allowed workflow-specific context bundle tools to enter ordinary
keyword-ranked recommendations. An authored component insertion could
therefore be classified as visual work and receive
`inspect-design-context` even without a supplied visual reference or a
request to reuse the project design system.

## Implementation

- Give specialized goal guides stable IDs and return structured routing
metadata with the matched workflow and broad context tools.
- Keep `inspect-auth-context` and `inspect-design-context` exclusive to
their specialized workflows.
- Treat Figma, screenshots, supplied designs, and explicit design-system
references as visual intent without treating every use of `design` as
visual work.
- Suppress component/template registry discovery for explicit
`insert-fragment` or authored-fragment requests.
- Suppress design-token operations for those requests unless the brief
explicitly asks for design-system or token reuse.
- Preserve the existing design-input workflow for Figma, screenshots,
and supplied designs.

## Todo

- [x] Add a regression test for the reported authored timer/component
workflow.
- [x] Demonstrate that the regression test fails before the routing fix
and passes afterward.
- [x] Preserve explicit design-input discovery and verification
behavior.
- [x] Add machine-readable workflow routing metadata.
- [x] Review documentation impact and update the authoritative MCP
manual plus generated GitBook output.
- [x] Review fixture impact; no fixture inputs or generated fixture
bundles are affected.
- [ ] Run the complete local agent evaluation suite. It was not run
because repository policy requires separate explicit approval and
approval was not provided.

## Verification

- Project-session MCP tests: 117 passed.
- Project-build and CLI typechecks passed.
- Targeted oxlint passed.
- Documentation generator tests and documentation typecheck passed.
- MCP documentation and CLI generated docs were regenerated twice with
stable hashes.
- `git diff --check` passed.

## Tradeoffs and limitations

- Brief classification remains deterministic intent matching; clients
still decide which recommended tools they actually call.
- The change prevents broad context tools from being recommended
generically but does not enforce a universal discovery-call limit.
- A fragment that genuinely needs existing tokens, unfamiliar
expressions, or unknown target structure can still require focused
discovery.
- No schema migration, project-data migration, or fixture regeneration
is required.
2026-08-20 13:13:45 +02:00
Oleg Isonen df3601c26a fix: Capture preview failure diagnostics (#6206)
## Summary

- Give invalid project bundles a stable `PROJECT_BUNDLE_INVALID` error
code and structured validation issues.
- Remember the latest failed MCP tool call and attach sanitized
diagnostics to automatic issue reports.
- Render the tool, error code, bundle contract, and invalid schema paths
in a dedicated issue section.

## Scope and issue assessment

This PR improves diagnostics; it does not change which asset shapes
`preview.start` accepts.

Issue #6198 came from CLI 0.290.0 and did not include the rejected
field, value, or bundle contract, so its exact root cause cannot be
established. Node 22 is not implicated by the available evidence.
Canonical video assets were added separately in #6189, which may explain
an old-CLI schema mismatch, but that relationship is an inference rather
than a confirmed reproduction.

Per project triage policy, this PR closes #6198 despite being
diagnostics-only. If the failure still exists on a current CLI, the
enriched report will provide enough evidence to reopen or file a new
actionable issue.

## Implementation

Bundle validation exposes structured paths such as `assets.0.type`. The
MCP host records the latest failure and injects it into CLI-owned
runtime metadata when `report-issue` is called.

The MCP registry supplies either a canonical known tool name or
`unknown`. Stable error codes are validated once by the CLI error-code
boundary. Detailed paths are included only for `PROJECT_BUNDLE_INVALID`,
capped at 30 issues, and omit received values, error messages, examples,
filenames, IDs, details, and stack traces. The bundle-contract field
remains optional so reports from older CLI versions still validate.

CLI-owned runtime metadata is not advertised as model-authored MCP
input, keeping the handshake compact and making ownership explicit.

## Checklist

- [x] Add structured bundle validation diagnostics.
- [x] Capture the latest MCP failure in issue-report runtime metadata.
- [x] Render deterministic diagnostics in GitHub issue bodies.
- [x] Add fail/pass regression coverage and privacy constraints.
- [x] Keep CLI-owned runtime fields out of the model-facing MCP schema.
- [x] Regenerate the server-only API contract.
- [x] Review documentation impact — no product documentation changes are
required; this only enriches internal anonymous issue reports.
- [ ] Run agent evaluations — not run because repository policy requires
explicit approval.

## Verification

- Real Node 22.22.1 stdio MCP run with an isolated malformed local
bundle returned `PROJECT_BUNDLE_INVALID`, path `assets.0.type`, code
`invalid_value`, and constraint `one_of:["image"]`.
- The following real `report-issue` call was accepted by the server and
deduplicated to existing issue #6198; it created no duplicate issue and
changed no linked project.
- Project-build: 2,115 tests passed.
- CLI: all 1,005 unaffected tests passed; one unrelated
process-supervisor timing test failed once and its exact two
owner-disconnect cases passed on immediate rerun.
- Project-build and CLI typechecks passed.
- Formatter and diff checks passed.
- All latest GitHub workflows passed, including both test/typecheck
matrices, visual regression, size and generated checks, packaged SSG,
and all six Builder E2E shards.

Closes #6198
2026-08-20 11:58:17 +02:00
Oleg Isonen f2cece41ee fix: Reconnect responsive screenshots after preview restart (#6205)
## Summary

Reconnect the reusable browser capture session and retry once when a
managed screenshot reports that its Browser DevTools connection closed.

The active generated-site preview remains running, so the retry
recreates only the failed browser session and replays the requested
single or multi-viewport capture.

## Root cause

Vision already performs a bounded recovery inside its browser runtime.
If that recovery still surfaced a DevTools session closure, the MCP
preview handler reset the failed session during error cleanup but
returned the failure to the caller. The next manual screenshot call
could succeed, but the first screenshot after a preview restart still
failed.

## Technical choices

- Give browser-session closure errors a stable typed code at the Vision
boundary.
- Detect that typed failure in the MCP preview handler.
- Close and recreate only the reusable browser capture session.
- Retry the complete capture once against the existing active preview.
- Apply the same recovery to one-viewport and responsive batch captures.
- Report the reconnect as a screenshot lifecycle progress checkpoint.

## Impact

A one-viewport responsive screenshot after preview restart now recovers
automatically from a closed DevTools connection. Other screenshot errors
retain their existing behavior, and repeated connection failure remains
bounded.

## Checklist

- [x] Add a fail-first regression for preview restart followed by a
one-viewport responsive capture
- [x] Recreate the failed browser session without restarting the active
preview
- [x] Retry the complete screenshot request once
- [x] Preserve existing behavior for non-session errors
- [x] Verify the recovery through a real preview server, Chrome, CDP,
rendering, and PNG output
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected
- [x] Review documentation impact: no update is required because this
restores documented automatic screenshot behavior
- [ ] Run agent evaluations (requires separate explicit approval)

## Verification

- Regression failed before the implementation with `Browser DevTools
connection closed.` and passed after it
- Real-browser harness with Google Chrome:
- restarted a real local HTTP preview and captured a full-page
`1440×900` PNG successfully on the first call
- forcibly closed the real CDP socket during `Page.captureScreenshot`
twice and confirmed the lower browser retry surfaced the typed
session-closed failure
- confirmed the MCP handler created a new browser session, replayed the
full capture successfully, and emitted `tool screenshot reconnecting
browser session`
  - confirmed browser recovery did not restart the active preview
- closed all Chrome processes, preview resources, temporary profiles,
and screenshot artifacts
- `pnpm --filter webstudio exec vitest run
src/commands/mcp-preview.test.ts` — 44 tests passed
- `pnpm --filter @webstudio-is/vision test` — 13 files, 81 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm --filter @webstudio-is/vision typecheck`
- `pnpm exec oxlint --deny-warnings
packages/vision/src/screenshot-browser-cdp.ts
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts`
- `pnpm exec oxfmt packages/vision/src/screenshot-browser-cdp.ts
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts`
- `git diff --check`

The DevTools failures were injected deterministically at the screenshot
command. The ordinary post-restart capture used the same real preview
and Chrome path without fault injection.

Agent evaluations were not run because repository policy requires
separate explicit approval.

Closes #6200
2026-08-20 01:02:26 +02:00
Oleg Isonen b2d62b2e49 fix: Restore scoped descendant selectors (#6195)
## Summary

Restore custom state selectors that target descendants of the selected
instance, including `:hover .button` and `:hover > .icon`.

The CSS engine already generated these selectors correctly. #5886
unintentionally rejected them while hardening MCP style inputs against
selectors that can escape instance scope.

## Technical choices

- Parse exactly one selector with `css-tree`.
- Require the selector to begin with a pseudo-class, pseudo-element, or
attribute state anchored to the selected instance.
- Allow only descendant and direct-child combinators after that anchor.
- Continue rejecting selector lists, unanchored type/class/ID selectors,
sibling combinators, unknown pseudo-selectors, and pseudo-elements used
as ancestors.
- Document CSS variables as the preferred parent-child interaction
pattern; use descendant selectors when selector-based targeting is
specifically required.

## Todo

- [x] Restore scoped descendant and direct-child selectors.
- [x] Preserve instance-scope protections.
- [x] Cover real authoring inputs and generated CSS.
- [x] Demonstrate new tests fail without the restored behavior and scope
guards.
- [x] Verify the reported workflow through the local CLI/MCP and
generated preview.
- [x] Review documentation impact and update the authoritative Builder
docs.
- [x] Verify generated API contracts remain current.

## Verification

- `pnpm --filter @webstudio-is/css-data test` — 4,623 passed, 1 skipped
- `pnpm --filter @webstudio-is/css-data typecheck`
- `pnpm --filter @webstudio-is/project-build exec vitest run
src/runtime/styles.test.ts src/runtime/audit.test.ts` — 198 passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm lint`
- `pnpm check:generated-api`
- Documentation targets and anchors verified
- Local MCP disposable-fixture workflow:
- Discovered `insert-fragment` and `update-styles` through MCP metadata.
  - Inserted a card with a descendant `<button class="button">`.
- Dry-run and committed `state: ":hover .button"`; `get-styles` returned
the persisted state.
- MCP preview generated `.chjrfuf:hover .button` while the card used the
`chjrfuf` instance class, proving the selector remained
instance-anchored.
  - Preview screenshot completed and returned HTTP 200.
- Agent evaluations were not run because the suite has no
custom-selector coverage

## Known limitation

Sibling combinators remain unsupported because they can select elements
outside the selected instance. Selector lists remain unsupported because
later list entries can escape the generated instance prefix.

Closes #6175
2026-08-20 00:20:03 +02:00
Oleg Isonen b1d23f69c0 fix: Report unsupported Node versions at CLI startup (#6201)
## Summary

- Reject unsupported Node.js versions before loading the CLI bundle.
- Report the declared engine range and detected version with an
install-and-retry instruction.
- Keep the runtime check aligned with `package.json#engines.node`.

## Root cause

The CLI declares Node.js 22.12+ but did not enforce that requirement at
startup. Package managers can warn and continue, allowing unsupported
runtimes to reach preview code. Execa 10 then initializes
`Set.prototype.union()`, which is unavailable in Node 20 and causes the
delayed `screenshot.responsive` failure.

## Technical choices

- Validate the runtime directly at the published executable boundary.
- Parse the declared engine range with `semver`.
- Dynamically import the CLI only after the runtime passes validation.
- Report the exact engine range rather than assuming every valid range
means “or newer.”
- Test the published `bin.js` in a subprocess with an injected
unsupported runtime version.
- Write the actionable startup failure to stderr and exit nonzero.
- No migrations or public API changes.

## Checklist

- [x] Add the startup runtime guard.
- [x] Add executable-level regression coverage for unsupported Node.js.
- [x] Verify the supported runtime still loads the CLI.
- [x] Review fixture impact; no fixture inputs or outputs are affected.
- [x] Review authoritative documentation; `docs/university/cli.md`
already requires Node.js 22.12+, so no update is needed.
- [ ] Run agent evaluations — not run because the requester declined
them.

## Verification

- Published and local launcher tests: 4 passed.
- CLI typecheck: passed.
- Targeted lint and formatting: passed.
- CLI production build: passed.
- Node.js 18.16.1 and 20.12.2 executable checks: exited 1 before loading
the CLI, reported the `>=22.12.0` requirement, and wrote no stdout.
- Node.js 22 executable check: CLI loaded successfully.
- Package dry run: includes the guarded `bin.js` and no separate runtime
helper.
- `git diff --check`: passed.
- The executable-level regression test was confirmed to fail against the
previous message/implementation and pass after the direct guard was
applied.

## Known limitation

An MCP client may wrap a subprocess startup failure in its own UI, but
the CLI now provides the actionable requirement and installation
instruction on stderr before any tool runs.

Closes #6190
2026-08-20 00:17:52 +02:00
Oleg Isonen a2e01a8ebe fix: Export configured Webhook Form methods (#6192)
## Summary

- Add an optional metadata contract for copying allowlisted resource
fields into generated component props.
- Pass action resources into page component generation.
- Configure Webhook Form's resource-bound `action` prop to generate the
form's `method` prop.
- Preserve explicitly authored props over generated resource props.
- Add generic generator, schema safety, rendered static-export
regression coverage, and updated generated fixture output.

## Root cause

Builder stores a Webhook Form's selected method on its action resource,
not as an ordinary instance prop. Component generation serialized the
resource-bound action identifier but had no generic contract for copying
safe resource fields into component props. The generated component
therefore received `action={"action"}` without the configured method,
and plain HTML rendering omitted the `method` attribute.

## Implementation

Resource prop metadata can now declare same-named fields using
`generatedProps`:

```ts
generatedProps: ["method"]
```

The component generator resolves those declarations generically; it
contains no Webhook Form component checks. The schema currently
allowlists only `method`, so resource URLs, headers, bodies, and other
potentially sensitive configuration cannot be generated as component
props. An explicit instance prop with the same name takes precedence.

Dynamic routed Webhook Forms retain their existing browser-to-server
POST behavior. This change preserves configured resource metadata in
generated component output and does not add static Webhook submission
support.

## Todo

- [x] Reproduce the missing method from an existing resource-bound
Webhook Form.
- [x] Demonstrate the generator regression test fails against the
previous output.
- [x] Define a typed, allowlisted generated-props contract for
resources.
- [x] Apply generated props generically without component-name checks.
- [x] Preserve explicit instance props.
- [x] Verify a real SSG prerender contains `method="post"`.
- [x] Verify unsafe resource fields such as `url` cannot be generated.
- [x] Give the build/prerender regression test the established 30-second
timeout.
- [x] Regenerate and verify the affected Webstudio Features fixture.
- [x] Run affected lint, typechecks, unit suites, builds, and CLI
prebuild tests.
- [x] Review documentation impact; no update is needed because
configuration and static-site support constraints are unchanged.

## Verification

- Regression test before implementation: failed because resource-bound
forms contained `action` but no generated `method`.
- `@webstudio-is/sdk`: 517 tests passed; focused schema test passed
after the rename.
- `@webstudio-is/sdk-components-react`: 126 passed, 1 skipped.
- `@webstudio-is/react-sdk`: 140 tests passed; focused generator test
passed after the rename.
- CLI prebuild: 59 tests passed; focused rendered static HTML test
passed in 2.7 seconds with a 30-second test timeout.
- Webstudio Features fixture: regeneration is idempotent; typecheck and
production build passed.
- Typechecks: SDK, SDK components, React SDK, and CLI.
- Targeted formatting and `oxlint --deny-warnings`.
- React SDK and CLI builds.
- `git diff --check origin/main...HEAD`.
- Agent evaluations were not run because approval was not requested.

Closes #6191
2026-08-19 02:28:07 +02:00
Oleg Isonen 2df130171f feat: Resolve asset references as structured metadata (#6184)
## Summary

- add exact `$ref` asset values that resolve to Asset Manager metadata
- keep plain local asset paths returning URL strings for backward
compatibility
- derive reference metadata from the canonical asset object so newly
added asset fields flow through automatically
- let Assets query output fields select only the metadata returned,
including nested arrays
- carry the same resolved shape through preview, generated preview,
published runtime, and static export
- document the syntax and accessible image binding in Content Engine and
MCP docs

## Design

The source document declares only the relationship:

```yaml
featureImage:
  $ref: ./assets/hero.png
```

The Assets query controls projection with fields such as
`properties.featureImage.src` and `properties.featureImage.description`.
Selecting `properties.featureImage` returns the canonical Asset Manager
asset metadata together with the resolved `src`. The Content Engine
runtime contract accepts the canonical asset object instead of
maintaining a second metadata field list; `url` becomes `src`, and
internal `contentRef` is not exposed.

Plain strings retain their existing URL-string result. Existing document
`$ref` behavior is preserved by resolving structured asset metadata only
when the target is a known non-document asset.

## Verification

- [x] Content Engine full test suite: 298 tests
- [x] SDK full test suite: 517 tests
- [x] Asset repository tests: 39 tests
- [x] CLI prebuild tests: 58 tests
- [x] Content Engine, SDK, Asset Uploader, CLI, and Project Build
typechecks
- [x] Repository lint
- [x] Content Engine and MCP documentation generation and tests
- [x] Generated API check
- [x] Local fixture build and generated fixture update
- [x] New behavior tests demonstrated fail before/pass after the
implementation
- [ ] Complete fixture sync (development API currently rejects this
branch's newer CLI protocol; local fixture build passes)
- [ ] Agent evaluations (not run; require explicit approval)

## Checklist

- [x] Local structured references return field-selected metadata
- [x] Newly added canonical asset fields flow through without a Content
Engine whitelist change
- [x] Plain-path queries remain backward compatible
- [x] Nested object and array projection
- [x] External URL strings remain usable
- [x] Missing or null descriptions allow `??` fallback to the title
- [x] Expanded metadata counts toward query result-size limits
- [x] Documentation impact reviewed and Content Engine and MCP docs
updated
- [ ] Missing asset targets return a non-failing unresolved value and
dedicated diagnostic
- [ ] Apply the resource and Image binding changes to the separate
Webstudio blog project

## Known limitation

A `$ref` whose asset target no longer exists is not yet converted into
an unresolved asset value with a dedicated diagnostic. This PR
intentionally does not claim that acceptance criterion yet.

Ref #6179
2026-08-19 01:11:41 +02:00
Oleg Isonen d4a876bc35 fix: Report terminated MCP single-op calls (#6187)
## Summary

- return structured `MCP_CALL_TERMINATED` output when a one-shot MCP
call receives SIGHUP, SIGINT, or SIGTERM, or reaches `beforeExit`
without a result
- reuse the existing bounded host-cleanup and signal-preservation path
from `mcp run`
- cover the reported anchor → inline SVG → `pointer-events: none`
fragment through the real MCP normalization path

## Evidence and scope

The reported fragment itself does not hang: it normalizes in about 20–30
ms on both CLI 0.290.0 and current main, including Node 26.0.0. The
reproducible gap is in the surrounding process lifecycle: `mcp run`
reports interrupted calls, while `mcp single-op-call` had no termination
handlers. An external caller timeout could therefore end a one-shot call
without a structured result.

The original long-running project-session condition could not be
reproduced with the available credentials. This PR fixes the missing
termination diagnostic; it does not claim that nested SVG parsing caused
the stall.

## Technical choices

- Install termination handling before input parsing and project-session
startup so every active phase is covered.
- Re-check termination after every asynchronous preflight phase so
refreshes and mutations cannot start during cleanup.
- Stop reporting the call as active once success or failure output is
ready, preventing duplicate JSON during cleanup.
- Preserve the original terminating signal after bounded cleanup instead
of converting it to an ordinary exit.
- Do not add an arbitrary operation timeout: a timed-out committed
mutation can still finish remotely and would make retry safety
ambiguous.

## Todo

- [x] Reproduce the reported fragment on CLI 0.290.0, current main, and
Node 26.0.0
- [x] Add structured termination reporting to `mcp single-op-call`
- [x] Cover the nested SVG style input and termination payload
- [x] Exercise the installed one-shot `beforeExit` lifecycle, not only
the payload formatter
- [x] Stop execution after asynchronous checkpoint preflight resumes
from termination
- [x] Review documentation impact — existing CLI docs already promise
structured JSON for failed one-shot calls; no content update is required

## Verification

- Nested-SVG reporter regression demonstrated fail-before
(`reportMcpSingleOpCallTermination is not a function`) and pass-after
- Installed-lifecycle regression demonstrated fail-before when the
active-call hook was disabled and pass-after when restored
- `pnpm --filter webstudio exec vitest run src/commands/mcp.test.ts` —
43 passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings packages/cli/src/commands/mcp.ts
packages/cli/src/commands/mcp.test.ts`
- `pnpm --filter webstudio exec vitest run src/bin.test.ts` — 3 passed
- `pnpm --filter webstudio test` — 998 passed; two unrelated
concurrent-suite timeouts (`src/bin.test.ts` and
`evaluations/high-impact/fixture-api.test.ts`). The launcher test passed
in isolation. The evaluation fixture was not rerun because evaluations
require separate approval.

## Known limitations

- SIGKILL cannot be intercepted, so no process can emit a final
diagnostic in that case.
- This change does not impose an internal operation deadline or cancel
in-flight mutations.
- A real authenticated reproduction is still needed to identify why the
original project-session call remained active.

Ref #6132
2026-08-19 00:23:45 +02:00
Oleg Isonen 0760d6cfec fix: upload videos from Builder and MCP (#6189)
## Summary

Video uploads failed because file detection returned `video` while the
canonical Asset schema accepted only `font | image | file`. The mismatch
was introduced when video detection was added in #5539; the recent JSON
work only exposed the adjacent contract gap and did not cause it.

This makes `video` a first-class `Asset["type"]` across Builder, SDK,
storage formatting, project bundles, HTTP uploads, and MCP. There is no
separate upload-only type and no conversion from `video` to `file`.

## Implementation

- Add the canonical `video` Asset variant with width/height metadata.
- Make Builder UI reservations and optimistic assets preserve `video`.
- Make MCP and HTTP upload schemas derive their enum from the canonical
SDK `assetType`.
- Preserve video metadata through S3 and filesystem upload paths.
- Include canonical video assets in project/canvas bundles.
- Keep legacy video records without dimension metadata readable as
generic `file` assets.
- Fall back to a labeled file preview when the browser cannot decode a
video format such as AVI; browser-supported formats continue to render
video thumbnails.
- Regenerate runtime operation contracts and sync fixtures from the PR
deployment.

## Contract protection

- Assert every detected upload category parses as a canonical Asset
type.
- Exercise Builder reservations for every value in `assetType.options`.
- Assert MCP's upload enum exactly equals `assetType.options`.
- Exercise direct HTTP video upload metadata and URL construction.
- Cover canonical video formatting, legacy fallback, preview assets, and
canvas serialization.
- Verify a video decode error replaces the blank media element with a
visible format-labeled fallback.
- Store canonical WebM and MP4 records with dimensions in the
`webstudio-features` fixture and verify generated runtime metadata.

## Checklist

- [x] Make video a canonical Asset type
- [x] Verify Builder UI upload flow
- [x] Verify MCP and direct HTTP upload contracts
- [x] Add fail/pass regression coverage for the broken paths
- [x] Add an Assets panel fallback for browser-unsupported video formats
- [x] Regenerate runtime contracts
- [x] Sync and build fixtures against the PR deployment
- [x] Run affected package tests, typechecks, lint, package-boundary,
generated API, and generated docs checks
- [x] Review documentation impact — existing Assets documentation
already lists supported video formats and upload behavior; no
user-facing documentation change is required

## Verification

- Builder: 2,543 tests passed
- Builder asset thumbnail regression: 14 tests passed
- Project build/MCP runtime: 2,111 tests passed
- CLI: 999 tests passed
- SDK: 516 tests passed
- Asset uploader: 199 tests passed
- Protocol: 92 tests passed
- HTTP client: 38 tests passed
- Typechecks passed for Builder, SDK, asset uploader, project build,
protocol, HTTP client, and CLI
- Lint and package-boundary checks passed
- Generated API and documentation checks passed
- Fixture sync completed against
`fix-video-up-be4d8a0.development.webstudio.is`
- All fixture builds passed after synchronization

Closes #6186
2026-08-19 00:21:11 +02:00
Oleg Isonen 5a57234c75 revert: Store Content Block content in MDX assets
Revert the accidental merge of #6125 so the implementation can continue in draft PR #6181.
2026-08-18 13:23:32 +01:00
Oleg Isonen 5d10a7de60 fix: Recover responsive screenshots after DevTools disconnects (#6170)
## Summary

Recover responsive screenshot batches when the Browser DevTools
WebSocket closes transiently.

The capture session now treats a DevTools socket closure as the same
recoverable session failure as an unexpected browser exit. It restarts
Chromium once and reruns the complete viewport batch, so callers receive
a consistent set of screenshots instead of partial artifacts.

## Root cause

The DevTools socket close handler rejected pending commands with a
generic `Error`. The reusable browser retry boundary only recovered
typed browser-session closures or a browser process that had exited, so
a closed socket with a still-running Chromium process escaped as
`MCP_TOOL_FAILED`.

## Technical choices

- Classify a DevTools WebSocket closure as `BrowserSessionClosedError`
at the CDP boundary.
- Reuse the existing bounded browser-session restart path instead of
adding a separate MCP-layer retry.
- Retry the complete responsive batch once so results remain
all-or-nothing.
- Preserve the original `Browser DevTools connection closed.` diagnostic
when the replacement connection also closes.

## Impact

A transient DevTools disconnect during a responsive capture
automatically restarts the reusable browser and captures every requested
viewport. Repeated connection loss still fails after one retry without
looping indefinitely.

## Checklist

- [x] Add a fail-first regression for a two-viewport responsive capture
- [x] Verify mobile and desktop widths are both recaptured after
reconnect
- [x] Verify a second consecutive disconnect returns the original error
- [x] Independently verify recovery with real Chrome, CDP, rendering,
and PNG output
- [x] Review fixture impact: no fixture inputs or generated fixture
outputs are affected
- [x] Review documentation impact: no user-facing documentation changes
are required because this restores documented behavior
- [ ] Run agent evaluations (requires separate approval)

## Verification

- New responsive reconnect regression failed before the implementation
with `Browser DevTools connection closed.` and passes after it
- Independent real-browser harness with Google Chrome `151.0.7922.138`:
- served a real local responsive page and captured `375×812` and
`1440×900`
- forcibly closed the first real CDP WebSocket during
`Page.captureScreenshot`
- observed Chrome #1 terminate, its profile get removed, Chrome #2
launch, and the complete viewport batch replay
- produced valid PNGs at both requested dimensions and left no browser
processes or temporary profiles behind
- `pnpm --filter @webstudio-is/vision test` — 13 files, 81 tests passed
- `pnpm --filter @webstudio-is/vision typecheck`
- `pnpm --filter webstudio exec vitest run
src/commands/mcp-preview.test.ts` — 43 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings
packages/vision/src/screenshot-browser-cdp.ts
packages/vision/src/screenshot-browser-cdp.test.ts`
- `pnpm exec oxfmt packages/vision/src/screenshot-browser-cdp.ts
packages/vision/src/screenshot-browser-cdp.test.ts`
- `git diff --check`

The real-browser fault was injected deterministically at the first
screenshot command rather than produced by an organic OS or network
failure. It exercised the reported DevTools closure boundary and
complete responsive-batch recovery.

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

Closes #6169
2026-08-18 12:53:50 +02:00
Oleg Isonen 5ec3547d36 fix: Support JSON content files in Builder (#6168)
## Summary

- make the complete asset filename editable in Asset settings
- convert existing editable text assets when their extension changes
while preserving the asset ID
- accept every JSON root type and normalize JSON-compatible syntax into
formatted strict JSON
- expose the same optional extension change through MCP/API
`update-asset-content`
- initialize new JSON files and empty text-to-JSON conversions with a
valid empty object
- distinguish browser URL imports from JSON file uploads so JSON objects
with a root `url` field remain file content
- update the Assets and Content Engine documentation

## Root cause

Asset settings rendered the extension as a fixed suffix outside the name
input. The metadata rename mutation could only change the display
basename, while content revisions always inherited the current storage
extension. As a result, changing `.md` to `.json` was impossible in the
UI and unavailable through the shared MCP/API revision command.

Asset settings now edits the complete filename. Ordinary renames keep
using the metadata mutation. A text-extension change loads the current
content, validates it for the target format, and writes a new immutable
revision through the existing conflict-safe content endpoint. The
existing Acorn-backed `parseJsonExpression` utility accepts
JSON-compatible syntax without evaluating code and serializes it as
strict JSON. Objects, arrays, strings, numbers, booleans, and `null` are
all accepted.

## Checklist

- [x] Create JSON files by entering a `.json` filename
- [x] Edit the complete filename in Asset settings
- [x] Convert editable text-file extensions while preserving the asset
ID
- [x] Expose extension conversion through `update-asset-content`
- [x] Create JSON files and convert empty text files with a valid empty
object
- [x] Normalize safe JSON-compatible syntax to strict JSON
- [x] Accept objects, arrays, and scalar JSON roots
- [x] Reject executable or incomplete content
- [x] Preserve browser-uploaded JSON object shapes
- [x] Review and update authoritative Builder documentation
- [x] Add focused UI, revision, HTTP-client, CLI/MCP, protocol, and
upload regression tests
- [x] Run affected typechecks, lint, documentation checks, and the
Builder production build
- [ ] Manually verify create, edit, rename `.md` to `.json`, reload
persistence, and Content Engine query in a browser
- [ ] Run the complete local agent evaluation suite if approved

## Verification

- 217 focused tests passed across Builder, asset revisions, HTTP client,
CLI/MCP, and protocol
- Builder, asset-uploader, HTTP-client, protocol, CLI, and project-build
typechecks passed
- Builder production build passed
- MCP and Content Engine documentation checks passed
- `oxlint` on changed TypeScript files passed
- `git diff --check` passed
- Regression tests were demonstrated failing before the implementation
and passing afterward

A disposable E2E database and PostgREST stack bootstrapped successfully
in the earlier real-path test, but UI automation remained blocked
because no controllable browser was available. Fixture inputs and
generated fixture outputs are not affected. Agent evaluations were not
run because approval is required and was not requested.

Closes #6167
2026-08-18 11:39:01 +02:00
Oleg Isonen 81d5148046 fix: canonicalize trailing-slash page URLs (#6173)
## Outcome

Canonical page URLs now omit a trailing slash in Webstudio Cloud sites
and exported JavaScript applications. A request such as
`/about/?campaign=launch` receives a permanent `301` redirect to
`/about?campaign=launch`, preventing both URL variants from serving
identical page content.

The behavior is implemented once in the generated root redirect handler,
so it covers fixed and dynamic application routes without generating an
extra route per page. Exact project redirects remain authoritative.
Protocol-relative-looking paths such as `//example.com/` are left
untouched so canonicalization cannot become an external redirect.

Static exports cannot redirect requests themselves. Their URL behavior
remains host-specific and is documented for Cloudflare Pages, Netlify,
Vercel, and other static hosts.

## Implementation

- Add one shared pathname normalizer in the SDK.
- Apply canonicalization after exact authored redirect matching in both
generated application runtimes.
- Ignore repeated leading slashes before constructing a redirect
location.
- Extend redirect-loop detection to model the implicit canonical
redirect.
- Regenerate the affected fixture applications from their templates.
- Document application canonicalization and host-specific static export
behavior.

## Todo

1. [x] Add central trailing-slash canonicalization.
2. [x] Preserve query strings, encoded path segments, and the root path.
3. [x] Cover concrete dynamic URLs without per-route generation.
4. [x] Preserve explicit project redirect precedence.
5. [x] Detect redirect loops that pass through canonicalization.
6. [x] Prevent protocol-relative external redirects.
7. [x] Regenerate and review fixture output.
8. [x] Review documentation impact; document application and SSG
behavior.
9. [x] Run targeted and repository-level verification.

## Verification

- SDK tests: 548 passed
- SDK typecheck: passed
- Project Build tests: 2,379 passed
- Project Build typecheck: passed
- CLI tests: 997 passed; two tests timed out only while three package
suites ran concurrently, then both affected files passed in isolation
(6/6)
- Redirect regression suite after security follow-up: 302 passed
- CLI typecheck: passed
- `pnpm lint`: passed
- `pnpm check:package-boundaries`: passed
- `pnpm fixtures`: passed
- Documentation internal anchors and Markdown whitespace: checked
- New regression tests were confirmed failing before their
implementations and passing afterward
- Agent evaluations were not run because they require separate explicit
approval

Closes #6172
Closes https://github.com/webstudio-is/webstudio/issues/4327
2026-08-18 11:38:27 +02:00
Oleg Isonen 86b6310fff feat: Store Content Block content in MDX assets 2026-08-17 15:22:20 +01:00
Oleg Isonen ab5717c9cf feat: Support CSS properties of every status (#6166)
## Summary

- Make every CSS property in the current MDN dataset available in the
Advanced style workflow, regardless of standards status.
- Mark experimental, nonstandard, and obsolete properties with a yellow
warning icon, status-specific tooltip, and MDN link.
- Add complete CSS anchor-positioning property and value support.
- Update `mdn-data` from 2.28.0 to 2.33.0 and `css-tree` from 3.1.0 to
3.2.1.

## Root cause

Webstudio generated its supported property list from MDN data but
filtered by status and then manually allowed a few experimental and
nonstandard entries. A property could therefore exist in the installed
MDN package without being exposed by the Builder.

Runtime validation had a separate grammar gap. CSS-tree 3.2.1 now
includes the anchor functions and alignment values, but its bundled data
predates the current `position-anchor: normal | match-parent` additions.

## Implementation

- Remove status-based property filtering and the nonstandard-property
allowlist.
- Generate one property-to-status map for 486 standard, 40 experimental,
119 nonstandard, and 15 obsolete properties.
- Show status-specific browser-compatibility guidance for every
non-standard status. Active declarations keep the warning after the
value with 3px left spacing.
- Infer MDN links from existing property metadata and fall back to MDN
search for entries without a direct URL.
- Preserve known vendor prefixes from the generated property inventory
instead of maintaining a prefix allowlist.
- Generate WebKit text fill/stroke longhands directly from MDN instead
of preserving them through parser exceptions.
- Preserve unsupported initial-value shapes as unparsed CSS rather than
excluding their properties.
- Use CSS-tree 3.2.1's native anchor-function and alignment grammars.
Keep one narrow extension for the current `position-anchor` keywords
missing from its bundled MDN data.
- Generate recursively flattened shorthand longhands and initial values
from MDN.
- Expand syntax-defined shorthands generically from CSS-tree's matched
property captures, including function-valued components.
- Decline generic expansion when CSS-tree and MDN disagree about the
longhand set or a longhand repeats.
- Update the font grammar reference and CSS math validation for CSS-tree
3.2.1 compatibility.
- Regenerate CSS metadata and property-variable fixtures.

## Checklist

- [x] Include MDN properties regardless of status.
- [x] Generate status metadata for standard, experimental, nonstandard,
and obsolete properties.
- [x] Show warning icons in Add styles results and active declarations.
- [x] Add status-specific consequences and MDN links.
- [x] Preserve generated vendor-prefixed properties during CSS import.
- [x] Generate WebKit text fill/stroke longhands without parser
exceptions.
- [x] Support `anchor()`, `anchor-size()`, and `anchor-center`.
- [x] Support current `position-anchor` keywords.
- [x] Expand syntax-compatible shorthands through generic MDN metadata
and CSS-tree captures.
- [x] Cover ordered optional, unordered, slash-separated,
function-valued, omitted, CSS-wide, variable, invalid, and
incompatible-data cases.
- [x] Verify new status, warning, prefix, and shorthand tests fail
without their implementations and pass when restored.
- [x] Update to CSS-tree 3.2.1.
- [x] Regenerate CSS metadata and property-variable fixtures.
- [x] Review documentation impact; no authoritative Builder
documentation lists individual supported CSS properties, so no update is
needed.
- [x] Review fixture impact; 668 fixtures and 2,030 valid cases were
regenerated with zero unsupported.

## Verification

- `pnpm --filter @webstudio-is/css-data test` — 4,618 passed, 1 skipped.
- Builder CSS editor and property-status tests — 25 passed.
- CSS data, CSS engine, Project Build, and Builder typechecks passed.
- Lint, formatting, repeated regeneration, and diff checks passed.

## Known limitation

This change covers CSS declarations and values. It does not add a
dedicated Builder editor for the `@position-try` at-rule.

Closes #6162
2026-08-17 14:54:11 +02:00
Oleg Isonen af11ec211f feat: Search all local project data from MCP (#6165)
Ref #6154

## What

- Add standalone search across all 13 synchronized Builder namespaces.
- Search in the CLI ProjectSession and send only paged matches to the
model; initial synchronization is unchanged.
- Return stable match ids, exact paths, entities, references, and
affected routes.
- Preserve deprecated `scopes` output, redact recognized credential
fields, and document the command.

## Objectives

- [x] O1 — Universal Builder namespace search
- [x] O2 — Stable identity and actionable location
- [x] O3 — Filters, pagination, route impact, and references
- [x] O4 — Honest synchronization → local search → model-input boundary
- [x] O5 — Standalone command and workflow documentation
- [x] O6 — Safety, compatibility, typed contracts, and regression tests

Verified by Atlas (implementation), Scribe (docs/workflow), and Sentinel
(adversarial review).

## Remaining #6154 work

- [ ] Atomic semantic multi-record updates
- [ ] Targeted route rendering and assertions
- [ ] General slow-operation consent and time budgets
- [ ] Full agent evaluation suite after approval

## Verification

- Project search + MCP: 129 tests passed
- Protocol docs: 8 tests passed
- CLI schema/docs: 23 tests passed
- Compact MCP handshake budget test passed
- Project-build, protocol, and CLI typechecks passed
- Generated docs and API checks passed
- Agent evaluations not run; approval is required

## Limitation

Asset metadata is searchable. Asset binaries and document contents are
not Builder namespace data and are not searched.
2026-08-17 14:08:25 +02:00
Oleg Isonen 4ca695cf46 fix: Improve MCP screenshot and session compatibility (#6163)
## Summary

Allow responsive MCP screenshots to use local project data and remove
the CLI session storage dependency on the Web Crypto global.

## Root causes

- Responsive capture rejected every `source: "local"` request before
preview preparation, even though the generated preview and reusable
browser session support local data.
- Local screenshot inputs skipped collision-free port resolution.
- Project session revisions and restore-point IDs called
`crypto.randomUUID()` through the ambient global. Environments without
the Web Crypto global failed after otherwise successful mutations.
- The production/session route-generation failure from #6161 is no
longer reproducible on current `main`; the exact reported flow succeeds,
so this PR does not add a speculative change for it.

## Technical choices

- Resolve an available preview port for local generated screenshots
using the existing screenshot port path.
- Permit local and session sources through the same responsive
generated-preview capture path.
- Keep mixed preview targets and mixed browser configurations rejected.
- Import `randomUUID` from `node:crypto` for session revisions and
restore points.
- Preserve explicit URL and `baseUrl` capture behavior.

## Impact

A single `screenshot.responsive` call can prepare a local project once,
reuse the configured browser, and capture desktop, tablet, and mobile
viewports. Native MCP mutations can persist their session state without
requiring the Web Crypto global.

## Checklist

- [x] Add fail-first coverage for local responsive preview preparation
and port allocation
- [x] Add fail-first coverage for session persistence without the Web
Crypto global
- [x] Verify a real local CLI capture at 1440×900, 768×1024, and 390×844
- [x] Verify the production/session scenario from #6161 on current
`main`
- [x] Run the complete CLI test suite
- [x] Run CLI typecheck and lint
- [x] Review documentation impact: no user documentation changes are
required because this restores the documented MCP behavior
- [ ] Run agent evaluations (requires explicit approval)

## Verification

- `pnpm --filter webstudio test` — 62 files, 983 tests passed
- `pnpm --filter webstudio typecheck`
- `pnpm exec oxlint --deny-warnings
packages/cli/src/commands/mcp-preview.ts
packages/cli/src/commands/mcp-preview.test.ts
packages/cli/src/project-session.ts
packages/cli/src/project-session.test.ts`
- `git diff --check`
- Real local-source production capture returned HTTP 200 at all three
viewports using one generated preview and the requested Chrome
executable

Closes #6156
Closes #6160
Ref #6161
2026-08-17 12:31:10 +02:00
Oleg Isonen baa48232fc fix: Keep Assets routes out of the static sitemap (#6158)
## Summary

- keep Assets-backed dynamic path discovery and SSG prerendering
unchanged
- stop merging Assets-derived paths into `$resources.sitemap.xml`
- keep eligible static pages in the generated sitemap
- update the existing Assets prerender integration test to cover static
sitemap behavior

## Why

The CLI used one Assets query result for both route generation and
sitemap indexing. Renderability and indexability are separate concerns,
and automatic insertion could duplicate entries from a custom
`/sitemap.xml` page. The Builder already exposes only static sitemap
data, so generated production output should match it.

## Compatibility and release note

Assets-backed dynamic routes are no longer added automatically to the
static sitemap. Existing projects that relied on that behavior need to
add those routes explicitly to a custom `/sitemap.xml` page with an
Assets resource and Collection. This `fix:` PR is included in the
repository's generated release notes under **Fixes**.

## Technical choices

- Removed only the Assets-derived sitemap collection and merge.
- Left `getAssetResourcePrerenderPaths()`, query evaluation, route
generation, prerendering, and draft filtering unchanged.
- Continued using `getStaticSiteMapXml()` as the sole source of
generated sitemap entries.

## Verification

- Regression demonstration before the implementation change: the updated
targeted test failed because `/blog/hello-world` was present in
`$resources.sitemap.xml.ts`.
- After the implementation change, the targeted integration test passed
and successfully built/prerendered `/blog/hello-world`.
- `pnpm --filter webstudio test -- src/prebuild.test.ts` — 58 tests
passed.
- `pnpm --filter webstudio typecheck` — passed.
- `pnpm exec oxfmt --check packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts` — passed.
- `pnpm exec oxlint --deny-warnings packages/cli/src/prebuild.ts
packages/cli/src/prebuild.test.ts` — passed.
- Agent evaluations were not run, per request.
- Fixture regeneration was not needed because no fixture inputs or
outputs changed.

## Todo

- [x] Invert the existing Assets prerender sitemap assertion.
- [x] Demonstrate that the regression test fails with the old behavior.
- [x] Remove Assets-derived sitemap collection and merging.
- [x] Confirm dynamic Assets routes still build and prerender.
- [x] Confirm drafts stay excluded and eligible static pages stay
included.
- [x] Review documentation impact.
- [x] Review the authoritative `docs/` content: the Content Engine guide
already directs users to build and draft-filter a custom sitemap for
dynamic Assets entries, and the XML Node guide documents combining it
with the static Sitemap resource; no docs change is required.
- [x] Document the compatibility change for generated release notes.

Closes #6157
2026-08-15 22:52:45 +02:00
Oleg Isonen dbfb0a9849 fix: Preserve variable font styles (#6153)
## Summary

Preserve font style metadata for variable fonts so upright and italic
files from the same family produce distinct `@font-face` rules. Variable
fonts with an `ital` or `slnt` axis are also registered for the matching
CSS styles.

## Technical choices

- Detect `normal`, `italic`, and `oblique` from the font binary,
preferring OS/2 `fsSelection`, then `post.italicAngle`, then the
existing subfamily parser.
- Add `style` to variable-font metadata while defaulting legacy stored
records to `normal`.
- Keep update metadata partial and strict, but accept and persist
`style` and `variationAxes` together so a `get-asset` result can be
written back.
- Group generated font faces by family, style, and browser-selectable
variable descriptors so separate upright and italic files cannot
overwrite each other.
- Regenerate the runtime operation contracts from the updated schema.

## Checklist

- [x] Add regressions using real upright and italic Inter variable-font
binaries.
- [x] Cover style detection, metadata compatibility, updates, and
generated font-face selection.
- [x] Verify the final stylesheet contains distinct upright and italic
`@font-face` rules.
- [x] Verify the public asset update contract accepts a complete
variable-font metadata object.
- [x] Review documentation impact and update the custom-font guide.
- [x] Regenerate and verify runtime API contracts.

## Verification

- `@webstudio-is/fonts`: 14 tests and typecheck passed.
- `@webstudio-is/asset-uploader`: 198 tests and typecheck passed.
- `@webstudio-is/sdk`: 514 tests and typecheck passed.
- `@webstudio-is/project-build`: 2,094 tests and typecheck passed.
- CLI: 980 tests and typecheck passed.
- Builder typecheck passed.
- `pnpm lint`, `pnpm check:package-boundaries`, `pnpm
check:generated-api`, and `pnpm check:generated-docs` passed.
- Regression tests were demonstrated failing against the previous
behavior and passing with the fix.
- Fixture builds regenerated successfully with no diff. Full remote
fixture sync was attempted, but the deployed API rejected the source CLI
contract as incompatible.
- Agent evaluations were not run because they require separate explicit
approval.

Closes #6150
2026-08-15 22:52:00 +02:00
Oleg Isonen 95c867697f feat: Make MCP verification proportional (#6155)
## Summary

Make MCP verification proportional to the requested task and require an
explicit preflight before a full production preview starts.

This is the first vertical slice of the broader fast-workflow work in
#6154. It fixes the current guidance and production-preview escalation
behavior without claiming the universal search, cross-record update, or
targeted route-evaluation APIs are complete.

## Technical choices

- Classify `meta.guide` requests as:
  - small value or reference correction
  - focused page change
  - visual change
  - structural project change
  - project-wide migration
- Give small corrections the explicit workflow `Focused search → atomic
edit → targeted assertions` and omit the visual loop.
- Include full-production-preview cost and faster-alternative metadata
in goal guidance.
- Change mutation follow-up guidance so focused assertions are
sufficient for small corrections; reserve audit and screenshot work for
structural, visual, or runtime-dependent outcomes.
- Add `maxDurationMs`, `confirmSlow`, and `confirmationToken` to
`preview.start`.
- Return a non-running preflight for production mode with an honest
30–60 second estimate, purpose, faster alternative, and short-lived
token.
- Bind confirmation tokens to the normalized preview input and time
budget so changed requests require a fresh preflight.

## Tradeoffs and compatibility

- Iterative preview behavior is unchanged.
- Production preview now requires one additional confirmed call.
- The successful `preview.start` output still reports running state and
mode; `url` is optional because a preflight has not started a server.
- This PR does not yet add universal content search, match IDs,
cross-kind atomic updates, or a targeted route renderer. Those remain in
#6154.

## Todo

- [x] Classify MCP task scope in `meta.guide`
- [x] Recommend focused search → atomic edit → targeted assertions for
small corrections
- [x] Stop prescribing preview/screenshots for non-visual small
corrections
- [x] Add production-preview time-budget preflight
- [x] Require an unchanged short-lived confirmation token before
production preview
- [x] Add fail-before/pass-after regression coverage
- [x] Review documentation impact: generated MCP docs are unchanged; no
authored `docs/` update is required for this internal contract increment
- [ ] Add universal search results with stable match IDs and
route/location context
- [ ] Add one cross-kind atomic semantic update operation
- [ ] Add lightweight route assertions and targeted route rendering
- [ ] Apply the generic slow-operation contract to other broad audits,
crawls, and multi-capture workflows
- [ ] Add historical timing inputs to cost estimates

## Verification

- `pnpm --filter @webstudio-is/project-build test -- --run
src/mcp.test.ts` — 116 passed
- `pnpm --filter @webstudio-is/project-build typecheck`
- `pnpm exec oxlint --deny-warnings packages/project-build/src/mcp.ts
packages/project-build/src/mcp.test.ts`
- `pnpm docs:mcp:sync` — no generated diff
- `pnpm docs:mcp:test` — 3 passed
- `pnpm docs:mcp:typecheck`
- Agent evaluations were not run because they require separate explicit
approval.

Ref #6154
2026-08-15 15:10:35 +02:00