Skip to content

feat: replace asset option for image assets in asset details#5676

Merged
kof merged 15 commits intowebstudio-is:mainfrom
shreyanshkotak:replace-assets
Apr 10, 2026
Merged

feat: replace asset option for image assets in asset details#5676
kof merged 15 commits intowebstudio-is:mainfrom
shreyanshkotak:replace-assets

Conversation

@shreyanshkotak
Copy link
Copy Markdown
Contributor

@shreyanshkotak shreyanshkotak commented Apr 3, 2026

Description

Adds the ability to replace an existing image asset with a new file directly from the asset details panel, without having to delete and re-upload manually. Related to place an image so that it gets replaced automatically everywhere its being used #3268

When clicking the new replace button on an image asset:

  1. A file picker opens filtered to image MIME types
  2. The new file is uploaded via the existing upload pipeline
  3. All references to the old asset (props, styles, page meta: favicon, social image, marketplace thumbnail) are atomically re-pointed to the new asset
  4. The old asset's filename and description are copied to the new asset
  5. The old asset is deleted

A new ReverseIcon from the Figma library was added to the icons package to represent the replace action.

Steps for reproduction

  1. Open the asset manager and select any image asset
  2. In the asset details panel, click the new replace icon button next to the download button
  3. Select a new image file from the file picker
  4. Expect the asset to be replaced in place - all usages (image props, background styles, favicon, social image) should now reference the new file with the original asset's filename preserved

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 document
  • added tests
  • if any new env variables are added, added them to .env file

shreyanshkotak and others added 7 commits April 2, 2026 23:01
Add a 'Replace Asset' button (RefreshIcon) next to the download button
in the asset details popover. This is only shown for image-type assets.

When triggered, the user selects a new image file which is uploaded via
the existing pipeline. All references to the original asset (props,
styles, page meta) are atomically re-pointed to the new asset in a
single transaction. The original asset's filename and description (alt
text) are preserved on the replacement. The old asset is then deleted.

- New module: replace-asset.ts with replaceAsset() function
- Updated asset exports in index.ts
- Modified asset-info.tsx to render replace button (disabled in view mode)
Add a new ReverseIcon and use it for the
asset replace action in the builder UI.
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Comment thread apps/builder/app/builder/shared/assets/replace-asset.ts Outdated
Use $assets.subscribe() instead of a setTimeout loop so waitForAsset
resolves as soon as the upload completes, regardless of network speed.
Removes the arbitrary 60s timeout and 500ms polling interval.
Props with type "asset" hold an asset ID reference and can not
represent width or height, which are always type "number" per the
schema. The name checks were dead code.
@shreyanshkotak
Copy link
Copy Markdown
Contributor Author

@kof resolved all the items mentioned in the review

@shreyanshkotak shreyanshkotak requested a review from kof April 10, 2026 02:35
@github-actions
Copy link
Copy Markdown

🚀 Deployed!

📍 Preview: https://pr-5676.development.webstudio.is

Note: Adding new commits will remove the safe-to-deploy label and require re-approval.

@kof
Copy link
Copy Markdown
Member

kof commented Apr 10, 2026

Found a bug after testing:

  1. create an image instance, choose an image 1
  2. hit replace image to replace it for image 2
  3. Hit undo shortcut
  4. See we now have both images in the assets panel (not sure if this is expected but not a big deal imho)
  5. Delete image 2
  6. Try to replace image 1 again with the same image 2
  7. See error toast: "Cannot access 't' before initialization"
notification-popover-CjwxmI7J.js:14 Uncaught (in promise) ReferenceError: Cannot access 't' before initialization
    at notification-popover-CjwxmI7J.js:14:11937
    at Object.subscribe (index-3whys8t3.js:1:488)
    at notification-popover-CjwxmI7J.js:14:11893
    at new Promise (<anonymous>)
    at hT (notification-popover-CjwxmI7J.js:14:11866)
    at cT (notification-popover-CjwxmI7J.js:14:11095)

If I reload and do step 6 again - it will work.

$assets.subscribe() fires its callback synchronously, causing a "Cannot
access 't' before initialization" ReferenceError when the asset already
exists in the store (e.g. after undo → delete → re-replace).
Check current value with .get() first for an early return, and use
.listen() instead of .subscribe() to avoid the synchronous callback.
@shreyanshkotak
Copy link
Copy Markdown
Contributor Author

@kof good catch! This was a TDZ bug in waitForAsset — nanostores .subscribe() fires synchronously, so when the asset already exists in the store (which happens in this undo → delete → re-replace flow), unsubscribe is referenced before assignment.

fixed in the latest commit.

@kof kof merged commit 72a4cec into webstudio-is:main Apr 10, 2026
13 of 15 checks passed
@kof
Copy link
Copy Markdown
Member

kof commented Apr 10, 2026

merged 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants