🐛 Fix update library dialog when a component position changes

Do not show the library sync popup when the only differences are global x/y changes on library components. We now generate the actual sync changes and only notify if there are real redo-changes to apply.
Run cll/generate-sync-file-changes for candidate libraries and filter out those with empty :redo-changes. The expensive check is deferred via rx/timer 0 so it runs asynchronously and does not block the UI.
Why: Position-only changes are normalized during sync (via reposition-shape) and never propagate to copies; showing the popup in that case was a false positive.
Performance: The check is deferred to the next tick to avoid UI stutter on large files with many libraries.
This commit is contained in:
Pablo Alba
2026-05-13 22:02:00 +02:00
committed by Andrés Moya
parent 26e583c2a6
commit c1f48a9f0c
2 changed files with 48 additions and 24 deletions

View File

@@ -26,6 +26,7 @@
- Fix lost-update race on `team.features` during concurrent file creation (by @web-dev0521) [Github #9197](https://github.com/penpot/penpot/issues/9197)
- Fix copy and paste actions crashing the workspace on insecure origins (plain HTTP / non-`localhost`) where the Clipboard API is unavailable (by @MilosM348) [Github #6514](https://github.com/penpot/penpot/issues/6514)
- Fix blend-mode dropdown leaving the canvas rendered with the last hover-preview blend mode when dismissed without selecting an option; the WASM render is now reverted to the saved blend mode on pointer-leave (by @edwin-rivera-dev) [Github #XXXX](https://github.com/penpot/penpot/issues/XXXX)
- Fix update library dialog when a component position changes [Taiga #11981](https://tree.taiga.io/project/penpot/issue/11981)
## 2.16.0 (Unreleased)

View File

@@ -1281,38 +1281,61 @@
file (dsh/lookup-file state file-id)
file-data (get file :data)
ignore-until (get file :ignore-sync-until)
permissions (:permissions state)
permissions (:permissions state)
libraries-need-sync
(->> (vals (get state :files))
(filter #(= (:library-of %) file-id))
(filter #(seq (assets-need-sync % file-data ignore-until))))
(filter #(seq (assets-need-sync % file-data ignore-until))))]
do-more-info
#(modal/show! :libraries-dialog {:starting-tab "updates" :file-id file-id})
(if-not (and (:can-edit permissions)
(seq libraries-need-sync))
;; Fast path: no libraries need sync based on timestamps
(rx/empty)
do-update
#(do (apply st/emit! (map (fn [library]
(sync-file (:current-file-id state)
(:id library)))
libraries-need-sync))
(st/emit! (ntf/hide)))
;; Defer the expensive change generation check to avoid blocking the UI.
;; For files with many libraries, this prevents stuttering/freezing.
(->> (rx/timer 0)
(rx/map (fn [_]
;; This runs asynchronously on the next tick.
;; Filter libraries to only those that would produce actual sync changes.
(let [libraries (dsh/lookup-libraries state)]
(filter (fn [library]
(seq (:redo-changes
(cll/generate-sync-file-changes
(pcb/empty-changes)
nil
nil
file-id
nil
(:id library)
libraries
file-id))))
libraries-need-sync))))
(rx/filter seq)
(rx/map (fn [libraries-with-changes]
(let [do-more-info
#(modal/show! :libraries-dialog {:starting-tab "updates" :file-id file-id})
do-dismiss
#(st/emit! ignore-sync (ntf/hide))]
do-update
#(do (apply st/emit! (map (fn [library]
(sync-file file-id (:id library)))
libraries-with-changes))
(st/emit! (ntf/hide)))
(when (and (:can-edit permissions)
(seq libraries-need-sync))
(rx/of (ntf/dialog
:content (tr "workspace.updates.there-are-updates")
:controls :inline-actions
:links [{:label (tr "workspace.updates.more-info")
:callback do-more-info}]
:cancel {:label (tr "workspace.updates.dismiss")
:callback do-dismiss}
:accept {:label (tr "workspace.updates.update")
:callback do-update}
:tag :sync-dialog)))))))
do-dismiss
#(st/emit! ignore-sync (ntf/hide))]
(ntf/dialog
:content (tr "workspace.updates.there-are-updates")
:controls :inline-actions
:links [{:label (tr "workspace.updates.more-info")
:callback do-more-info}]
:cancel {:label (tr "workspace.updates.dismiss")
:callback do-dismiss}
:accept {:label (tr "workspace.updates.update")
:callback do-update}
:tag :sync-dialog))))))))))
(defn touch-component
"Update the modified-at attribute of the component to now"