shopware / shopware/shopware

Experience Studio: a refused save hangs silently and an empty layout cannot be previewed

Open
#19,944 0 comments 0 reactions 1 assignee Claimed by @marcelbrode View on GitHub
component/canvas domain/discovery priority/low
Dominant language
PHP
Stars
3.4k
Forks
1.2k
Avg merge
2d 23h
Merged PRs (30d)
433

Description

Three defects found by driving the Experience Studio end to end in a browser. All three predate the model split (#19663) and are not regressions from it. The first two together make a refused save indistinguishable from a hung one: the user sees a spinner and nothing else, forever.

## 1. A refused save gives no signal and never finishes

`sw-experience-studio-detail/index.ts::onSave()` awaits `layoutRepository.save()` with no `catch` and no `finally`. `isLoading` is set before the await and cleared only on the success path, past the success notification. A rejected save leaves the Save button spinning with no notification.

The path is reachable through the editor's own affordances: add an `Sw:Media:Image` element, leave its media unset, save.

```
PATCH /api/content-layout/{id} -> 400
{"errors":[{"code":"unfilled_required_input","status":"400",
"detail":"Required property \"media\" is wired from \"mediaId\", which has no value.", ...}]}
```

Reproduced twice, the second time after a clean page reload. The rejection itself is correct. The preview route runs only the intrinsic diagnostics subset, while resolvability is a write-time gate, so a draft that previews cleanly can still be refused at save. That split is deliberate, which makes the failure branch a permanent requirement of the save path, not a defensive extra.

Expected: `onSave()` catches the rejection, clears `isLoading` in a `finally`, surfaces the API error `detail` to the user, and leaves the draft intact for repair.

## 2. `ErrorResolver` crashes on a pointer segment that is not a field

`src/core/data/error-resolver.data.js:132` reads

```js
this.errorStore.addSystemError(error);
```

but `this.errorStore` is never assigned anywhere in the class; every other call site reaches the store via `Shopware.Store.get('error')`. Line 132 therefore throws `TypeError: Cannot read properties of undefined (reading 'addSystemError')`.

The branch runs whenever a write error's pointer carries a segment that is not a field of the written entity. The content-layout pointer `/0//media` is such a case: `` is not a field of `content_layout`, so `definition.getField()` returns undefined and the `!field` branch executes line 132. This is a platform defect, not a content-system one; any entity write with a non-field pointer segment hits it. The file was last changed by `cb265e7ae21`, untouched by the content-system work.

Expected: the `!field` branch reaches the error store the same way the rest of the class does.

## 3. Preview refuses an empty layout

`ContentPreviewRequest` declares `#[Assert\NotBlank]` on `array $layout`. `NotBlank` rejects `[]`. The studio requests a preview when a layout opens, so a freshly created layout's first screen is Symfony's generic "This value should not be blank." error.

It is the only one of the eleven content request DTOs that constrains `layout` this way; the mutation DTOs declare `#[Assert\Type('array')]` with a `[]` default and accept an empty layout today. The constraint dates to `3434e63d833`, the commit that introduced the preview action.

Expected: `#[Assert\Type('array')]` alone, matching the siblings, so an empty draft mints a preview token that renders the page with no layout elements. Precondition: confirm `ContentPreviewPageBuilder::build()` completes on an empty tree, or answer the empty case before the build.

## Out of scope

Concurrent editing: the save carries no concurrency token, so two editors are last-write-wins. That is owned by #19585. The failure branch from section 1 handles a future 409 from that work the same way it handles the 400 above.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.