Skip to content

[Studio][Assets] Harden asset save flow: never send empty save requests, don't wipe in-flight change tracking #403

Description

@mcop1

Context

Follow-up to PEES-1560 / pimcore/service-operations#1244, fixed minimally in pimcore/asset-metadata-class-definitions#442. That fix addresses the root cause in the metadata form's draft sync, but the investigation showed the asset save flow in studio-ui-bundle turns any late/lost draft sync into permanent, invisible data loss. The same trap exists for any current or future tab that syncs its draft asynchronously.

Defects in studio-ui-bundle (save-button.tsx + draft slices)

  1. Empty save requests are sent by design. The save button builds the payload from asset.changes flags and calls the mutation unconditionally — with no tracked changes it sends PUT /pimcore-studio/api/assets/{id} with data: {} (also for schedules-only saves, since schedules go to their own endpoint). The backend runs a full $asset->save() for it: a new version and a modification-date bump for nothing.
  2. Success wipes all change tracking. On success the button calls removeTrackedChanges(), which resets every change flag — including changes tracked while the request was in flight. Combined with (1): an empty save "succeeds", clears the flags the async draft sync just set, the query invalidation resets the draft to server values, and the user's edits vanish with a success toast.
  3. Latent empty-payload path: a change flag whose draft data is undefined produces e.g. metadata: undefined, which serializes to an empty payload.

Proposed changes (validated on a 2026.x working branch, incl. jest specs)

  • Extract payload assembly into a pure collectAssetUpdateData() (testable; omits sections whose draft data is undefined).
  • After the save-data processors ran: skip the asset PUT when the update is empty — schedules-only saves call only the schedules endpoint; a click with nothing to save just shows the toast.
  • New resetChangesForTypes reducer in use-trackable-changes.ts; on success clear only the change types snapshotted at request time (removeTrackedChanges(types?), backward compatible — no argument keeps full-reset behavior).
  • Regression specs for the collector and the reducer.

Notes

  • Observable behavior change to review: schedules-only saves will no longer create an asset version / bump the modification date (previously caused by the empty PUT).
  • Optional backend hardening (separate decision): UpdateService::update() in studio-backend-bundle saves unconditionally even for an empty data payload — it could short-circuit, but task-based flows need a BC check; the primary fix belongs in the UI.
  • Residual edge to consider while implementing: saving via keyboard shortcut while focus is still inside a form field doesn't blur, so an async draft sync can still lag one save behind — with the changes above this is no longer lossy (no empty PUT, no flag wipe; the next save picks it up). A synchronous pre-save flush hook via the AssetSaveDataProcessorRegistry would close it completely.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Affected capability

    None yet

    Platform Version

    None yet

    Galaxy

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions