From 0334d162efff5de6cd2517ec437c3ece720379be Mon Sep 17 00:00:00 2001 From: scheinriese Date: Tue, 19 May 2026 22:26:21 +0200 Subject: [PATCH] fix(selection): atomic batch icon writes via single outliner transact! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-block doseq path (avatar/text/clear) used to issue N independent transactions through the worker — partial mid-loop failures left the selection in a mixed state, no shared undo step, and N reactive re-renders. Wrap the doseq in one ui-outliner-tx/transact! with op-tag :set-block-properties. The inner handler calls each open their own transact! but short-circuit when an outer binding is active (per the macro at frontend/modules/outliner/ui.cljc), so all N ops collapse into a single atomic DB transaction, one undo step, and one re-render cascade. Co-Authored-By: Claude Opus 4.7 --- src/main/frontend/components/selection.cljs | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/frontend/components/selection.cljs b/src/main/frontend/components/selection.cljs index 8065a75d0b..8380e05569 100644 --- a/src/main/frontend/components/selection.cljs +++ b/src/main/frontend/components/selection.cljs @@ -6,6 +6,7 @@ [frontend.handler.db-based.property :as db-property-handler] [frontend.handler.editor :as editor-handler] [frontend.handler.property :as property-handler] + [frontend.modules.outliner.ui :as ui-outliner-tx] [frontend.state :as state] [frontend.ui :as ui] [frontend.util :as util] @@ -40,14 +41,21 @@ (defn- batch-write-icon! [blocks icon] (if (or (nil? icon) (contains? #{:avatar :text} (:type icon))) - ;; Per-block writes (avatar/text need per-row initials; clear/nil applies uniformly) - (doseq [block blocks] - (if (nil? icon) - (property-handler/remove-block-property! (:db/id block) :logseq.property/icon) - (db-property-handler/set-block-property! - (:db/id block) - :logseq.property/icon - (icon-data-for-block icon block)))) + ;; Per-block writes (avatar/text need per-row initials; clear/nil applies + ;; uniformly). Wrap the doseq in one outliner transact! so all N writes + ;; commit atomically — partial-failure used to leave the selection in a + ;; mixed state. The inner handler calls each open their own transact!, + ;; but the macro short-circuits when an outer binding is active, so this + ;; produces a single DB tx, undo step, and reactive re-render. + (ui-outliner-tx/transact! + {:outliner-op :set-block-properties} + (doseq [block blocks] + (if (nil? icon) + (property-handler/remove-block-property! (:db/id block) :logseq.property/icon) + (db-property-handler/set-block-property! + (:db/id block) + :logseq.property/icon + (icon-data-for-block icon block))))) ;; Uniform value across blocks (icon, emoji, image): single batch transaction (property-handler/batch-set-block-property! (map :db/id blocks)