From af7142e97b76ea46b08200df35d1beeef784c201 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Mon, 12 Feb 2024 11:49:34 +0100
Subject: [PATCH 01/23] :sparkles: New overlay for v2 information

---
 backend/src/app/rpc/commands/auth.clj         |  3 +-
 frontend/resources/images/icons/v2-icon-1.svg |  1 +
 frontend/resources/images/icons/v2-icon-2.svg |  1 +
 frontend/resources/images/icons/v2-icon-3.svg |  1 +
 frontend/resources/images/icons/v2-icon-4.svg |  1 +
 .../styles/common/refactor/fonts.scss         |  1 +
 frontend/src/app/main/data/workspace.cljs     | 13 ++--
 .../app/main/ui/dashboard/change_owner.scss   |  4 ++
 frontend/src/app/main/ui/icons.cljs           |  5 ++
 .../src/app/main/ui/workspace/libraries.cljs  | 56 ++++++++++++++++
 .../src/app/main/ui/workspace/libraries.scss  | 67 +++++++++++++++++++
 11 files changed, 148 insertions(+), 5 deletions(-)
 create mode 100644 frontend/resources/images/icons/v2-icon-1.svg
 create mode 100644 frontend/resources/images/icons/v2-icon-2.svg
 create mode 100644 frontend/resources/images/icons/v2-icon-3.svg
 create mode 100644 frontend/resources/images/icons/v2-icon-4.svg

diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj
index 66bec377d..8e9671e59 100644
--- a/backend/src/app/rpc/commands/auth.clj
+++ b/backend/src/app/rpc/commands/auth.clj
@@ -262,7 +262,8 @@
                       (merge (:props params))
                       (merge {:viewed-tutorial? false
                               :viewed-walkthrough? false
-                              :nudge {:big 10 :small 1}})
+                              :nudge {:big 10 :small 1}
+                              :v2-info-shown true})
                       (db/tjson))
 
         password  (or (:password params) "!")
diff --git a/frontend/resources/images/icons/v2-icon-1.svg b/frontend/resources/images/icons/v2-icon-1.svg
new file mode 100644
index 000000000..b39647eb1
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="4494 307 310 290"><path d="m4643.25 309.15-18.86 15.75a9.32 9.32 0 0 0-1.2 13.1 9.26 9.26 0 0 0 13.07 1.2l3.67-3.05.2 82.04a103.8 103.8 0 0 0-14.46-19.07c-15.68-16.23-36.46-25.7-59.17-26.11a86.83 86.83 0 0 0-11.04.52l1.91-3.29a9.32 9.32 0 0 0-3.42-12.57 9.26 9.26 0 0 0-12.6 3.2l-12.59 21.63a9.32 9.32 0 0 0 2.1 11.86l18.93 15.67a9.26 9.26 0 0 0 9.16 1.54 9.32 9.32 0 0 0 2.66-15.9l-4.42-3.66c21.64-2.66 40.4 4.82 55.14 20.07 16.68 17.27 27.77 45.12 27.98 79.86-.05 9.54.04 19.06.07 28.6-10.61-16.53-24.8-30.38-40.13-40.85-15.92-10.87-33.13-18.28-49.55-21.38-8.2-1.55-16.3-2.04-23.93-1.2-2.3.26-4.6.66-6.85 1.2l1.09-2.8a9.27 9.27 0 0 0-17.27-6.77l-9.1 23.32a9.32 9.32 0 0 0 3.91 11.4l21.13 12.53a9.26 9.26 0 0 0 12.54-3.37 9.32 9.32 0 0 0-3.1-12.65l-5.56-3.3c6.54-1.62 14.64-1.77 23.72-.05 13.42 2.53 28.56 8.92 42.53 18.46 27.92 19.09 50.72 50.2 50.72 87.3v.03l.02 5.3a9.3 9.3 0 0 0 9.3 9.29 9.29 9.29 0 0 0 9.25-9.32v-2.77c.5-37.1 19.92-67.3 48.22-89.72 13.17-9.62 27.44-15.99 39.99-18.46 10.08-2 18.76-1.4 25.26 1.19l-4 2.43a9.32 9.32 0 0 0-3.14 12.79 9.26 9.26 0 0 0 12.74 3.14l21.37-13a9.32 9.32 0 0 0 3.78-11.43l-9.17-22.82a9.27 9.27 0 1 0-17.21 6.96l1.72 4.29a50.78 50.78 0 0 0-11.87-2.93c-7.38-.92-15.18-.45-23.06 1.11-15.78 3.1-32.2 10.63-47.34 21.68a142.37 142.37 0 0 0-37.45 40.54l-.07-24.5c.65-34.78 11.26-64.22 26.98-82.93 15.4-18.33 34.64-26.71 56.88-22.07l-4.52 2.74a9.32 9.32 0 0 0-3 12.7 9.26 9.26 0 0 0 12.6 3.22l21.37-12.99a9.32 9.32 0 0 0 3.79-11.43l-9.17-22.83a9.27 9.27 0 0 0-12.08-5.15 9.32 9.32 0 0 0-5.14 12.11l1.5 3.74a73.41 73.41 0 0 0-19.47-1.8c-22.03.99-42.11 12.09-56.95 29.76a115.7 115.7 0 0 0-12.97 19.25l-.2-83.78 3.47 3.03a9.26 9.26 0 0 0 12.97-1.03 9.32 9.32 0 0 0-.8-13.02l-18.87-16.42a9.26 9.26 0 0 0-12.01-.13Z" paint-order="fill markers"/></svg>
diff --git a/frontend/resources/images/icons/v2-icon-2.svg b/frontend/resources/images/icons/v2-icon-2.svg
new file mode 100644
index 000000000..d384ee2ed
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-2.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="4870 326 249.25 261.8"><path fill="#000" d="M5050.75 326.04a8.57 8.57 0 0 0-7.77 3.58l-19.12 27.03a8.57 8.57 0 0 0 0 9.9l19.12 27.04a8.57 8.57 0 0 0 15.44-3.5 8.57 8.57 0 0 0-1.45-6.4l-8.43-11.92c13.34-.5 22.4 1.84 28.61 5.26 6.78 3.74 10.6 8.83 13.14 14.83 2.54 6 3.51 12.91 3.77 19.07.24 6.16-.26 11.02-.19 15.04a8.57 8.57 0 0 0 8.7 8.43 8.57 8.57 0 0 0 8.44-8.69c-.02-1.7.47-8.1.17-15.49-.3-7.37-1.4-16.26-5.1-25.03a46.95 46.95 0 0 0-20.64-23.17c-9.83-5.43-22.72-8.14-39.22-7.3l10.75-15.21a8.57 8.57 0 0 0-2.05-11.93 8.56 8.56 0 0 0-4.17-1.54Zm-158.97 1.7A21.92 21.92 0 0 0 4870 349.5v61.37a21.92 21.92 0 0 0 21.77 21.78h61.38a21.92 21.92 0 0 0 21.78-21.77v-61.37a21.92 21.92 0 0 0-21.77-21.79h-61.38Zm0 17.13h61.37a4.46 4.46 0 0 1 4.64 4.64v61.37a4.46 4.46 0 0 1-4.63 4.65h-61.38a4.45 4.45 0 0 1-4.63-4.65v-61.37a4.45 4.45 0 0 1 4.63-4.63Zm-5.1 134.52a8.57 8.57 0 0 0-8.43 8.7c.02 1.7-.47 8.1-.17 15.47.3 7.37 1.4 16.26 5.1 25.04a47 47 0 0 0 20.65 23.18c9.83 5.41 22.7 8.14 39.2 7.3l-10.74 15.2a8.57 8.57 0 0 0 8.44 13.4 8.57 8.57 0 0 0 5.56-3.51l19.1-27.04a8.57 8.57 0 0 0 0-9.9l-19.1-27.03a8.57 8.57 0 0 0-5.56-3.5 8.57 8.57 0 0 0-8.44 13.4l8.42 11.92c-13.34.5-22.4-1.82-28.6-5.24-6.78-3.74-10.6-8.83-13.14-14.83-2.54-6.02-3.51-12.92-3.77-19.08-.24-6.16.26-11.02.2-15.03a8.57 8.57 0 0 0-8.71-8.45Zm152.1 1.74a24.6 24.6 0 0 0-24.46 24.46v56.02a24.6 24.6 0 0 0 24.47 24.45h56a24.6 24.6 0 0 0 24.46-24.45v-56.02a24.6 24.6 0 0 0-24.45-24.46h-56.02Zm0 17.14h56.02a7.12 7.12 0 0 1 7.31 7.32v56.02a7.11 7.11 0 0 1-7.31 7.3h-56.02a7.12 7.12 0 0 1-5.23-2.07 7.12 7.12 0 0 1-2.09-5.23v-56.02a7.11 7.11 0 0 1 7.33-7.32Z" class="fills" color="#000" paint-order="fill markers"/></svg>
diff --git a/frontend/resources/images/icons/v2-icon-3.svg b/frontend/resources/images/icons/v2-icon-3.svg
new file mode 100644
index 000000000..bfc88da6d
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-3.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="4563 696 212.14 362.63"><path d="M4669.07 696a41.22 41.22 0 0 0-29.24 12.07l-64.76 64.75a41.47 41.47 0 0 0 0 58.49l8.39 8.39-8.39 8.38a41.47 41.47 0 0 0 0 58.5l8.38 8.38-8.36 8.37a41.47 41.47 0 0 0 0 58.49l64.73 64.73a41.48 41.48 0 0 0 58.5.01l64.74-64.73a41.49 41.49 0 0 0 0-58.51l-8.37-8.38 8.37-8.37a41.47 41.47 0 0 0 0-58.5l-8.38-8.37 8.39-8.38a41.47 41.47 0 0 0 0-58.5l-64.75-64.75a41.25 41.25 0 0 0-29.25-12.07Zm0 18.28a22.85 22.85 0 0 1 16.2 6.83l64.76 64.75a22.68 22.68 0 0 1 0 32.42l-64.74 64.75a22.68 22.68 0 0 1-32.43.01l-64.76-64.75a22.68 22.68 0 0 1 0-32.43l64.75-64.75a22.86 22.86 0 0 1 16.22-6.83Zm0 52.73c-4.3 0-8.62 1.62-11.85 4.85l-18.35 18.36a16.89 16.89 0 0 0 0 23.7l18.35 18.35a16.89 16.89 0 0 0 23.7 0l18.36-18.35a16.89 16.89 0 0 0-.01-23.7l-18.35-18.35a16.7 16.7 0 0 0-11.85-4.86Zm0 15.19c.34 0 .68.16 1.01.5l18.36 18.35c.66.67.68 1.38.01 2.05l-18.35 18.35c-.67.67-1.4.67-2.06 0l-18.36-18.35c-.66-.66-.65-1.39.01-2.05l18.35-18.35c.34-.33.68-.5 1.03-.5Zm-72.58 70.53 43.33 43.33a41.47 41.47 0 0 0 58.5.01l43.33-43.33 8.37 8.37a22.68 22.68 0 0 1 0 32.42l-64.75 64.75a22.68 22.68 0 0 1-32.43 0l-64.73-64.73a22.68 22.68 0 0 1 0-32.44Zm145.17 75.24 8.37 8.37a22.7 22.7 0 0 1 0 32.45l-64.74 64.74a22.7 22.7 0 0 1-32.44-.01l-64.73-64.74a22.67 22.67 0 0 1 0-32.42l8.36-8.37 43.33 43.33a41.47 41.47 0 0 0 58.5 0Z" paint-order="fill markers"/></svg>
diff --git a/frontend/resources/images/icons/v2-icon-4.svg b/frontend/resources/images/icons/v2-icon-4.svg
new file mode 100644
index 000000000..dc2443432
--- /dev/null
+++ b/frontend/resources/images/icons/v2-icon-4.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="4870 696 216.34 319.12"><path d="M5052.72 696a5.69 5.69 0 0 0-5.8 4.77c-1.72 10.62-4.08 20.19-7.8 27-3.71 6.79-8.19 10.82-15.77 12.25-6.11 1.16-6.17 9.92-.07 11.15 7.97 1.64 12.63 5.72 16.33 12.39 3.7 6.68 5.93 16.05 7.27 26.7.89 6.88 10.98 6.47 11.32-.46.83-18.37 4.47-27.31 8.37-31.76 3.9-4.45 8.67-5.56 15.17-6.85 5.8-1.12 6.24-9.26.6-11.01-7.53-2.35-12.77-6.54-16.73-12.94-3.95-6.4-6.46-15.15-7.41-26.06a5.69 5.69 0 0 0-5.48-5.18Zm-151.65 71.03c-17.13.1-31 13.97-31.07 31.1v185.9a31.21 31.21 0 0 0 31.07 31.09h136.32a31.24 31.24 0 0 0 31.1-31.08V871.7c0-6.01-1.92-12.93-6.51-17.91-21.13-22.9-59.2-64.23-73.79-78.82-4-4.01-10.62-7.95-17.97-7.95Zm0 18.43h65.88v71.29c0 6.42 4.03 12.41 8.53 15.12 4.51 2.71 9.05 3.33 13.15 3.33h61.43v108.83a12.44 12.44 0 0 1-12.67 12.66h-136.32a12.42 12.42 0 0 1-12.65-12.66v-185.9a12.44 12.44 0 0 1 12.65-12.67Zm84.31 13.15a5423.07 5423.07 0 0 1 54.24 58.17h-50.99a8.99 8.99 0 0 1-3.25-.62Z" paint-order="fill markers"/></svg>
diff --git a/frontend/resources/styles/common/refactor/fonts.scss b/frontend/resources/styles/common/refactor/fonts.scss
index 40d1dcb87..c4bc4cb3f 100644
--- a/frontend/resources/styles/common/refactor/fonts.scss
+++ b/frontend/resources/styles/common/refactor/fonts.scss
@@ -15,6 +15,7 @@ $fs-11: 0.688rem;
 $fs-12: math.div(12, $fs-base) + rem;
 $fs-14: math.div(14, $fs-base) + rem;
 $fs-16: math.div(16, $fs-base) + rem;
+$fs-18: math.div(18, $fs-base) + rem;
 $fs-24: math.div(24, $fs-base) + rem;
 $fs-36: math.div(36, $fs-base) + rem;
 
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 46d847794..0249bf2ca 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -35,6 +35,7 @@
    [app.main.data.events :as ev]
    [app.main.data.fonts :as df]
    [app.main.data.messages :as msg]
+   [app.main.data.modal :as modal]
    [app.main.data.users :as du]
    [app.main.data.workspace.bool :as dwb]
    [app.main.data.workspace.changes :as dch]
@@ -119,10 +120,14 @@
           (assoc :workspace-ready? true)))
 
     ptk/WatchEvent
-    (watch [_ _ _]
-      (rx/of (fbc/fix-bool-contents)
-             (fdf/fix-deleted-fonts)
-             (fbs/fix-broken-shapes)))))
+    (watch [_ state _]
+      (rx/of
+       (when (and (not (boolean (-> state :profile :props :v2-info-shown)))
+                  (features/active-feature? state "components/v2"))
+         (modal/show :v2-info {}))
+       (fbc/fix-bool-contents)
+       (fdf/fix-deleted-fonts)
+       (fbs/fix-broken-shapes)))))
 
 (defn- workspace-data-loaded
   [data]
diff --git a/frontend/src/app/main/ui/dashboard/change_owner.scss b/frontend/src/app/main/ui/dashboard/change_owner.scss
index 2da4ec233..c1f6bf7d9 100644
--- a/frontend/src/app/main/ui/dashboard/change_owner.scss
+++ b/frontend/src/app/main/ui/dashboard/change_owner.scss
@@ -50,3 +50,7 @@
     @extend .modal-danger-btn;
   }
 }
+
+.modal-msg {
+  color: var(--modal-text-foreground-color);
+}
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index e90cc022e..f65ff1676 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -479,6 +479,11 @@
 (def ^:icon cap-round (icon-xref :cap-round))
 (def ^:icon cap-square (icon-xref :cap-square))
 
+(def ^:icon v2-icon-1 (icon-xref :v2-icon-1))
+(def ^:icon v2-icon-2 (icon-xref :v2-icon-2))
+(def ^:icon v2-icon-3 (icon-xref :v2-icon-3))
+(def ^:icon v2-icon-4 (icon-xref :v2-icon-4))
+
 
 (def ^:icon loader-pencil
   (mf/html
diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs
index 3262f202b..871c53799 100644
--- a/frontend/src/app/main/ui/workspace/libraries.cljs
+++ b/frontend/src/app/main/ui/workspace/libraries.cljs
@@ -15,6 +15,7 @@
    [app.common.types.typographies-list :as ctyl]
    [app.common.uuid :as uuid]
    [app.main.data.modal :as modal]
+   [app.main.data.users :as du]
    [app.main.data.workspace.libraries :as dwl]
    [app.main.refs :as refs]
    [app.main.render :refer [component-svg]]
@@ -511,3 +512,58 @@
            [:& updates-tab {:file-id file-id
                             :file-data file-data
                             :libraries libraries}]]]]]]]]))
+
+(mf/defc v2-info-dialog
+  {::mf/register modal/components
+   ::mf/register-as :v2-info}
+  []
+  (let [handle-gotit-click
+        (mf/use-fn
+         (fn []
+           (modal/hide!)
+           (st/emit! (du/update-profile-props {:v2-info-shown true}))))]
+
+    [:div {:class (stl/css :modal-overlay)}
+     [:div {:class (stl/css :modal-dialog :modal-v2-info)}
+      [:div {:class (stl/css :modal-title)} "IMPORTANT INFORMATION ABOUT NEW COMPONENTS"]
+      [:div  {:class (stl/css :modal-content)}
+       [:div {:class (stl/css :info-content)}
+        [:div {:class (stl/css :info-block)}
+         [:div {:class (stl/css :info-icon)} i/v2-icon-1]
+         [:div {:class (stl/css :info-block-title)}
+          "One physical source of truth"]
+         [:div {:class (stl/css :info-block-content)}
+          "Main components are now found at the design space. They act as a single source "
+          "of truth and can be worked on with their copies. This ensures consistency and "
+          "allows better control and synchronization."]]
+
+        [:div {:class (stl/css :info-block)}
+         [:div {:class (stl/css :info-icon)} i/v2-icon-2]
+         [:div {:class (stl/css :info-block-title)}
+          "Swap components"]
+         [:div {:class (stl/css :info-block-content)}
+          "Now, you can replace one component copy with another within your libraries. "
+          "The swap components functionality streamlines making changes, testing "
+          "variations, or updating elements without extensive manual adjustments."]]
+
+        [:div {:class (stl/css :info-block)}
+         [:div {:class (stl/css :info-icon)} i/v2-icon-3]
+         [:div {:class (stl/css :info-block-title)}
+          "Graphic assets no longer exist"]
+         [:div {:class (stl/css :info-block-content)}
+          "Graphic assets now disappear, so that all graphic assets become components. "
+          "This way, swapping between them is possible, and we avoid confusion about "
+          "what should go in each typology."]]
+
+        [:div {:class (stl/css :info-block)}
+         [:div {:class (stl/css :info-icon)} i/v2-icon-4]
+         [:div {:class (stl/css :info-block-title)}
+          "Main components page"]
+         [:div {:class (stl/css :info-block-content)}
+          "You might find that a new page called 'Main components' has appeared in "
+          "your file. On that page, you'll find all the main components that were "
+          "created in your files previously to this new version."]]]
+
+       [:div {:class (stl/css :info-bottom)}
+        [:button {:class (stl/css :primary-button)
+                  :on-click handle-gotit-click} "I GOT IT"]]]]]))
diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss
index 2664f3209..3fa999a1e 100644
--- a/frontend/src/app/main/ui/workspace/libraries.scss
+++ b/frontend/src/app/main/ui/workspace/libraries.scss
@@ -228,6 +228,15 @@
       }
     }
   }
+
+  .modal-v2-info {
+    width: $s-664;
+    height: fit-content;
+
+    .modal-title {
+      font-size: $fs-18;
+    }
+  }
 }
 
 .item-contents {
@@ -245,3 +254,61 @@
     margin-inline: $s-4;
   }
 }
+
+.info-content {
+  margin-top: $s-32;
+  display: flex;
+  flex-direction: column;
+  gap: $s-24;
+
+  .info-block {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    column-gap: $s-20;
+    grid-template:
+      "icon title"
+      "icon content";
+  }
+
+  .info-icon {
+    grid-area: icon;
+    width: $s-52;
+    height: $s-52;
+    margin-top: $s-8;
+    border-radius: $br-circle;
+    background: $db-quaternary;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    svg {
+      width: $s-32;
+      height: $s-32;
+      fill: $da-primary;
+    }
+  }
+  .info-block-title {
+    grid-area: title;
+    font-size: $fs-16;
+    color: $df-primary;
+  }
+  .info-block-content {
+    grid-area: content;
+    font-size: $fs-14;
+    color: $df-secondary;
+    line-height: 1.2;
+  }
+}
+
+.info-bottom {
+  margin-top: $s-24;
+  margin-right: $s-8;
+  display: flex;
+  justify-content: flex-end;
+  .primary-button {
+    @extend .button-primary;
+    @include tabTitleTipography;
+    padding: $s-0 $s-16;
+  }
+}

From 39b5f10529a0577ac5c5dd85eed983f3c0748bbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Mon, 12 Feb 2024 17:40:13 +0100
Subject: [PATCH 02/23] :bug: Fix update main when there are swapped copies

---
 common/src/app/common/types/container.cljc    |  7 --
 common/src/app/common/types/file.cljc         | 21 +++++-
 .../app/main/data/workspace/libraries.cljs    |  6 +-
 .../data/workspace/libraries_helpers.cljs     | 74 ++++++++++++-------
 4 files changed, 68 insertions(+), 40 deletions(-)

diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc
index c217969e1..4265329ea 100644
--- a/common/src/app/common/types/container.cljc
+++ b/common/src/app/common/types/container.cljc
@@ -166,13 +166,6 @@
     :else
     (get-instance-root objects (get objects (:parent-id shape)))))
 
-(defn get-copy-root
-  "Get the top shape of the copy."
-  [objects shape]
-  (when (:shape-ref shape)
-    (let [parent (cfh/get-parent objects (:id shape))]
-      (or (get-copy-root objects parent) shape))))
-
 (defn inside-component-main?
   "Check if the shape is a component main instance or is inside one."
   [objects shape]
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index 3f8e7b3bd..1fd8b1f00 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -190,7 +190,7 @@
   "Locate the near component in the local file or libraries, and retrieve the shape
    referenced by the instance shape."
   [file page libraries shape & {:keys [include-deleted?] :or {include-deleted? false}}]
-  (let [root-shape     (ctn/get-copy-root (:objects page) shape)
+  (let [root-shape     (ctn/get-component-shape (:objects page) shape)
         component-file (when root-shape
                          (if (and (some? file) (= (:component-file root-shape) (:id file)))
                            file
@@ -218,10 +218,23 @@
         component-file      (get-in libraries [(:component-file top-instance) :data])
         component           (ctkl/get-component component-file (:component-id top-instance) true)
         remote-shape        (get-ref-shape component-file component shape)
-        component-container (get-component-container component-file component)]
+        component-container (get-component-container component-file component)
+        [remote-shape component-container]
+        (if (some? remote-shape)
+          [remote-shape component-container]
+          ;; If not found, try the case of this being a fostered or swapped children
+          (let [head-instance       (ctn/get-head-shape (:objects container) shape)
+                component-file      (get-in libraries [(:component-file head-instance) :data])
+                head-component      (ctkl/get-component component-file (:component-id head-instance) true)
+                remote-shape'       (get-ref-shape component-file head-component shape)
+                component-container (get-component-container component-file component)]
+            [remote-shape' component-container]))]
+
     (if (nil? remote-shape)
-      shape
-      (find-remote-shape component-container libraries remote-shape))))
+      nil
+      (if (nil? (:shape-ref remote-shape))
+        remote-shape
+        (find-remote-shape component-container libraries remote-shape)))))
 
 (defn get-component-shapes
   "Retrieve all shapes of the component"
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index 269701c78..1fab0ddcd 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -77,7 +77,11 @@
                 extract (cond-> {:type (:type change)
                                  :raw-change change}
                           shape
-                          (assoc :shape (str prefix (:name shape)))
+                          (assoc :shape (str prefix (:name shape))
+                                 :shape-id (str (:id shape)))
+                          (:obj change)
+                          (assoc :obj (:name (:obj change))
+                                 :obj-id (:id (:obj change)))
                           (:operations change)
                           (assoc :operations (:operations change)))]
             extract))]
diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
index d19e776f3..9f19e14e6 100644
--- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
@@ -886,7 +886,6 @@
                           (map #(redirect-shaperef %) children-inst)
                           children-inst)
 
-
           only-inst (fn [changes child-inst]
                       (add-shape-to-main changes
                                          child-inst
@@ -1088,10 +1087,8 @@
                                              root-main))
 
         update-original-shape (fn [original-shape new-shape]
-                                (if-not (:shape-ref original-shape)
-                                  (assoc original-shape
-                                         :shape-ref (:id new-shape))
-                                  original-shape))
+                                (assoc original-shape
+                                       :shape-ref (:id new-shape)))
 
         [_new-shape new-shapes updated-shapes]
         (ctst/clone-shape shape
@@ -1116,25 +1113,46 @@
                                            :obj shape'}))))
 
         mod-obj-change (fn [changes shape']
-                         (update changes :redo-changes conj
-                                 {:type :mod-obj
-                                  :page-id (:id page)
-                                  :id (:id shape')
-                                  :operations [{:type :set
-                                                :attr :component-id
-                                                :val (:component-id shape')}
-                                               {:type :set
-                                                :attr :component-file
-                                                :val (:component-file shape')}
-                                               {:type :set
-                                                :attr :component-root
-                                                :val (:component-root shape')}
-                                               {:type :set
-                                                :attr :shape-ref
-                                                :val (:shape-ref shape')}
-                                               {:type :set
-                                                :attr :touched
-                                                :val (:touched shape')}]}))
+                         (let [shape-original (ctn/get-shape page (:id shape'))]
+                           (-> changes
+                               (update :redo-changes conj
+                                       {:type :mod-obj
+                                        :page-id (:id page)
+                                        :id (:id shape')
+                                        :operations [{:type :set
+                                                      :attr :component-id
+                                                      :val (:component-id shape')}
+                                                     {:type :set
+                                                      :attr :component-file
+                                                      :val (:component-file shape')}
+                                                     {:type :set
+                                                      :attr :component-root
+                                                      :val (:component-root shape')}
+                                                     {:type :set
+                                                      :attr :shape-ref
+                                                      :val (:shape-ref shape')}
+                                                     {:type :set
+                                                      :attr :touched
+                                                      :val (:touched shape')}]})
+                               (update :undo-changes conj
+                                       {:type :mod-obj
+                                        :page-id (:id page)
+                                        :id (:id shape-original)
+                                        :operations [{:type :set
+                                                      :attr :component-id
+                                                      :val (:component-id shape-original)}
+                                                     {:type :set
+                                                      :attr :component-file
+                                                      :val (:component-file shape-original)}
+                                                     {:type :set
+                                                      :attr :component-root
+                                                      :val (:component-root shape-original)}
+                                                     {:type :set
+                                                      :attr :shape-ref
+                                                      :val (:shape-ref shape-original)}
+                                                     {:type :set
+                                                      :attr :touched
+                                                      :val (:touched shape-original)}]}))))
 
         del-obj-change (fn [changes shape']
                          (update changes :undo-changes conj
@@ -1161,7 +1179,8 @@
         parents    (cfh/get-parent-ids objects (:id shape))
         parent     (first parents)
         children   (cfh/get-children-ids objects (:id shape))
-        ids        (into [(:id shape)] children)
+        ids        (-> (into [(:id shape)] children)
+                       (reverse)) ;; Remove from bottom to top
 
         add-redo-change (fn [changes id]
                           (update changes :redo-changes conj
@@ -1190,12 +1209,11 @@
                      (update :redo-changes conj (make-change
                                                  container
                                                  {:type :reg-objects
-                                                  :shapes (vec parents)}))
-                     (add-undo-change (:id shape)))
+                                                  :shapes (vec parents)})))
 
         changes' (reduce add-undo-change
                          changes'
-                         children)]
+                         ids)]
 
     (if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
       changes

From d63e5f520e837fb7ce4cf5865094433ca5827034 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Mon, 12 Feb 2024 16:11:50 +0100
Subject: [PATCH 03/23] :bug: Fix opacity display when selecting multiple
 shapes

---
 .../ui/workspace/sidebar/options/menus/layer.cljs    | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
index bdfec509f..eea27c06c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs
@@ -23,11 +23,11 @@
 
 (defn opacity->string
   [opacity]
-  (if (= opacity :multiple)
-    ""
+  (if (not= opacity :multiple)
     (dm/str (-> opacity
                 (d/coalesce 1)
-                (* 100)))))
+                (* 100)))
+    :multiple))
 
 (mf/defc layer-menu
   {::mf/wrap-props false}
@@ -39,7 +39,7 @@
         blocked?           (:blocked values)
 
         current-blend-mode (or (:blend-mode values) :normal)
-        current-opacity    (:opacity values)
+        current-opacity    (opacity->string (:opacity values))
 
         state*             (mf/use-state
                             {:selected-blend-mode current-blend-mode
@@ -161,8 +161,8 @@
              :title (tr "workspace.options.opacity")}
        [:span {:class (stl/css :icon)} "%"]
        [:> numeric-input*
-        {:value (opacity->string current-opacity)
-         :placeholder (tr "settings.multiple")
+        {:value current-opacity
+         :placeholder "--"
          :on-change handle-opacity-change
          :min 0
          :max 100

From d3bf35869a7087b9db29439411e92c6fc68e34a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Tue, 13 Feb 2024 12:35:59 +0100
Subject: [PATCH 04/23] :bug: Fix font size for modal links

---
 frontend/src/app/main/ui/export.scss                | 2 +-
 frontend/src/app/main/ui/onboarding.scss            | 2 +-
 frontend/src/app/main/ui/onboarding/newsletter.scss | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/frontend/src/app/main/ui/export.scss b/frontend/src/app/main/ui/export.scss
index 0f687fe61..f528d2eeb 100644
--- a/frontend/src/app/main/ui/export.scss
+++ b/frontend/src/app/main/ui/export.scss
@@ -103,7 +103,7 @@
     color: var(--modal-text-foreground-color);
   }
   .modal-link {
-    @include titleTipography;
+    @include bodyLargeTypography;
     text-decoration: none;
     cursor: pointer;
     color: var(--modal-link-foreground-color);
diff --git a/frontend/src/app/main/ui/onboarding.scss b/frontend/src/app/main/ui/onboarding.scss
index 7c2e1b22f..66ecddbe3 100644
--- a/frontend/src/app/main/ui/onboarding.scss
+++ b/frontend/src/app/main/ui/onboarding.scss
@@ -78,7 +78,7 @@
 }
 
 .modal-link {
-  @include titleTipography;
+  @include bodyLargeTypography;
   color: var(--modal-link-foreground-color);
   margin: 0;
 }
diff --git a/frontend/src/app/main/ui/onboarding/newsletter.scss b/frontend/src/app/main/ui/onboarding/newsletter.scss
index 350b75499..86f854597 100644
--- a/frontend/src/app/main/ui/onboarding/newsletter.scss
+++ b/frontend/src/app/main/ui/onboarding/newsletter.scss
@@ -60,7 +60,7 @@
 }
 
 .modal-link {
-  @include titleTipography;
+  @include bodyLargeTypography;
   color: var(--modal-link-foreground-color);
   margin: 0;
 }

From 565bf5fbb8ca484085df5747df09bbac910562c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Tue, 13 Feb 2024 11:42:13 +0100
Subject: [PATCH 05/23] :bug: Fix padding of font size selector

---
 .../sidebar/options/menus/typography.scss     | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
index c2ab44d9a..bec81db5b 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss
@@ -284,19 +284,6 @@
       padding: 0;
       border: $s-1 solid var(--input-border-color);
       position: relative;
-      .font-size-select {
-        @include removeInputStyle;
-        @include titleTipography;
-        height: $s-32;
-        height: 100%;
-        width: 100%;
-        margin: 0;
-        padding: 0;
-        .numeric-input {
-          @extend .input-base;
-          padding-inline-start: $s-8;
-        }
-      }
 
       .icon {
         @include flexCenter;
@@ -342,6 +329,20 @@
   }
 }
 
+.font-size-select {
+  @include removeInputStyle;
+  @include titleTipography;
+  height: $s-32;
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  padding: $s-8;
+  .numeric-input {
+    @extend .input-base;
+    padding: 0;
+  }
+}
+
 .font-selector {
   @include flexCenter;
   position: absolute;

From c336cbe8ab5c42e5a302cc5f09e2b6c66a3b63c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Tue, 13 Feb 2024 12:01:35 +0100
Subject: [PATCH 06/23] :bug: Fix text transform buttons order

---
 .../ui/workspace/sidebar/options/menus/typography.cljs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
index f759c634a..5f945a406 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
@@ -409,14 +409,14 @@
                         :type "checkbox"
                         :value "uppercase"
                         :id "text-transform-uppercase"}]
-      [:& radio-button {:icon i/text-lowercase-refactor
-                        :type "checkbox"
-                        :value "lowercase"
-                        :id "text-transform-lowercase"}]
       [:& radio-button {:icon i/text-mixed-refactor
                         :type "checkbox"
                         :value "capitalize"
-                        :id "text-transform-capitalize"}]]]))
+                        :id "text-transform-capitalize"}]
+      [:& radio-button {:icon i/text-lowercase-refactor
+                        :type "checkbox"
+                        :value "lowercase"
+                        :id "text-transform-lowercase"}]]]))
 
 (mf/defc text-options
   {::mf/wrap-props false}

From 39cb4a081bac91a673af701ba379d98b1bc09ae2 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 10:46:09 +0100
Subject: [PATCH 07/23] :bug: Clean legacy features on binfile (v1) importation

---
 backend/src/app/binfile/v1.clj | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/backend/src/app/binfile/v1.clj b/backend/src/app/binfile/v1.clj
index 1e3a7b917..207b40ec9 100644
--- a/backend/src/app/binfile/v1.clj
+++ b/backend/src/app/binfile/v1.clj
@@ -517,6 +517,15 @@
               (update :object-id #(str/replace-first % #"^(.*?)/" (str file-id "/")))))
         thumbnails))
 
+(defn- clean-features
+  [file]
+  (update file :features (fn [features]
+                           (if (set? features)
+                             (-> features
+                                 (cfeat/migrate-legacy-features)
+                                 (set/difference cfeat/backend-only-features))
+                             #{}))))
+
 (defmethod read-section :v1/files
   [{:keys [::db/conn ::input ::project-id ::bfc/overwrite ::name] :as system}]
 
@@ -527,6 +536,7 @@
           file-id    (:id file)
           file-id'   (bfc/lookup-index file-id)
 
+          file       (clean-features file)
           thumbnails (:thumbnails file)]
 
       (when (not= file-id expected-file-id)

From e8a1c58c5d7b9e9d2c68cb057be0a176f43db6a7 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 11:14:54 +0100
Subject: [PATCH 08/23] :bug: Fix incorrect change detection on srepl helper
 process-file

---
 backend/src/app/srepl/helpers.clj | 2 +-
 backend/src/app/srepl/main.clj    | 3 ---
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj
index ba98c2968..9df761db0 100644
--- a/backend/src/app/srepl/helpers.clj
+++ b/backend/src/app/srepl/helpers.clj
@@ -168,7 +168,7 @@
                 (update-fn file libs opts)
                 (update-fn file opts))]
 
-    (when (and (some? file)
+    (when (and (some? file')
                (not (identical? file file')))
       (when validate? (cfv/validate-file-schema! file'))
       (let [file' (update file' :revn inc)]
diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj
index 16e9a0905..9851af8a3 100644
--- a/backend/src/app/srepl/main.clj
+++ b/backend/src/app/srepl/main.clj
@@ -380,9 +380,6 @@
   "Apply a function to all files in the database, reading them in
   batches. Do not change data.
 
-  The `on-file` parameter should be a function that receives the file
-  and the previous state and returns the new state.
-
   Emits rollback at the end of operation."
   [on-file & {:keys [max-items start-at with-libraries?]}]
   (letfn [(get-candidates [conn]

From 1cb6f433392b5b8506403da7c9b1b74911deb613 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 11:21:02 +0100
Subject: [PATCH 09/23] :paperclip: Add srepl fix function for disable fdata
 features

---
 backend/src/app/srepl/fixes.clj | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/backend/src/app/srepl/fixes.clj b/backend/src/app/srepl/fixes.clj
index 955b87f81..5e450e0d9 100644
--- a/backend/src/app/srepl/fixes.clj
+++ b/backend/src/app/srepl/fixes.clj
@@ -16,8 +16,26 @@
    [app.common.logging :as l]
    [app.common.uuid :as uuid]
    [app.db :as db]
+   [app.features.fdata :as feat.fdata]
    [app.srepl.helpers :as h]))
 
+(defn disable-fdata-features
+  [{:keys [id features] :as file} _]
+  (when (or (contains? features "fdata/pointer-map")
+            (contains? features "fdata/objects-map"))
+    (l/warn :hint "disable fdata features" :file-id (str id))
+    (-> file
+        (update :data feat.fdata/process-pointers deref)
+        (update :data feat.fdata/process-objects (partial into {}))
+        (update :features disj "fdata/pointer-map" "fdata/objects-map"))))
+
+(defn find-fdata-pointers
+  [{:keys [id features data] :as file} _]
+  (when (contains? features "fdata/pointer-map")
+    (let [pointers (feat.fdata/get-used-pointer-ids data)]
+      (l/warn :hint "found pointers" :file-id (str id) :pointers pointers)
+      nil)))
+
 (defn repair-file-media
   "A helper intended to be used with `srepl.main/process-files!` that
   fixes all not propertly referenced file-media-object for a file"

From c89a1b3b27023195e1384b697c062cea53488c1d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Fri, 9 Feb 2024 15:44:53 +0100
Subject: [PATCH 10/23] :bug: Fix extend button style in prototype tab

---
 .../styles/common/refactor/basic-rules.scss   |  11 +-
 .../styles/common/refactor/design-tokens.scss |   4 +
 .../sidebar/options/menus/interactions.cljs   |   4 +-
 .../sidebar/options/menus/interactions.scss   | 288 +++++++++---------
 4 files changed, 164 insertions(+), 143 deletions(-)

diff --git a/frontend/resources/styles/common/refactor/basic-rules.scss b/frontend/resources/styles/common/refactor/basic-rules.scss
index 29a9219f0..83f2c904c 100644
--- a/frontend/resources/styles/common/refactor/basic-rules.scss
+++ b/frontend/resources/styles/common/refactor/basic-rules.scss
@@ -125,10 +125,11 @@
   @include buttonStyle;
   @include flexCenter;
   @include focusTertiary;
+  --button-tertiary-border-width: $s-2;
   border-radius: $br-8;
   color: var(--button-tertiary-foreground-color-rest);
   background-color: transparent;
-  border: $s-2 solid transparent;
+  border: var(--button-tertiary-border-width) solid transparent;
   svg,
   span svg {
     stroke: var(--button-tertiary-foreground-color-rest);
@@ -136,7 +137,7 @@
   &:hover {
     background-color: var(--button-tertiary-background-color-hover);
     color: var(--button-tertiary-foreground-color-hover);
-    border: $s-2 solid var(--button-secondary-border-color-hover);
+    border-color: var(--button-secondary-border-color-hover);
     svg,
     span svg {
       stroke: var(--button-tertiary-foreground-color-hover);
@@ -144,7 +145,7 @@
   }
   &:active {
     outline: none;
-    border: $s-2 solid transparent;
+    border-color: transparent;
     background-color: var(--button-tertiary-background-color-active);
     color: var(--button-tertiary-foreground-color-active);
     svg,
@@ -169,7 +170,7 @@
 
 .button-icon-selected {
   outline: none;
-  border: $s-2 solid var(--button-icon-border-color-selected);
+  border-color: var(--button-icon-border-color-selected);
   background-color: var(--button-icon-background-color-selected);
   color: var(--button-icon-foreground-color-selected);
   svg {
@@ -183,7 +184,7 @@
   @include focusRadio;
   border-radius: $br-8;
   color: var(--button-radio-foreground-color-rest);
-  border: $s-1 solid var(--button-radio-background-color-rest);
+  border-color: $s-1 solid var(--button-radio-background-color-rest);
   svg,
   span svg {
     stroke: var(--button-radio-foreground-color-rest);
diff --git a/frontend/resources/styles/common/refactor/design-tokens.scss b/frontend/resources/styles/common/refactor/design-tokens.scss
index 67abb7774..799195f84 100644
--- a/frontend/resources/styles/common/refactor/design-tokens.scss
+++ b/frontend/resources/styles/common/refactor/design-tokens.scss
@@ -65,6 +65,9 @@
   --button-tertiary-border-color-focus: var(--color-accent-primary);
   --button-tertiary-foreground-color-focus: var(--color-foreground-primary);
 
+  --expand-button-icon-border-width: 0;
+  --expand-button-icon-border-width-selected: 0;
+
   --button-icon-foreground-color: var(--color-foreground-secondary);
   --button-icon-foreground-color-hover: var(--color-foreground-secondary);
   --button-icon-background-color-selected: var(--color-background-quaternary);
@@ -396,4 +399,5 @@
   --assets-item-name-foreground-color: var(--color-foreground-primary);
 
   --text-editor-selection-background-color: var(--la-tertiary-70);
+  --expand-button-icon-border-width-selected: 2px;
 }
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index c88dbc51b..f5f04bb7a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -403,10 +403,10 @@
 
 
     [:div {:class (stl/css-case  :element-set-options-group true
-                                 :open extended-open?)}
+                                 :element-set-options-group-open extended-open?)}
           ; Summary
      [:div {:class (stl/css :interactions-summary)}
-      [:div {:class (stl/css-case :extend-btn true
+      [:button {:class (stl/css-case :extend-btn true
                                   :extended extended-open?)
              :on-click toggle-extended}
        i/menu-refactor]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
index cc3679140..09c1b4207 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
@@ -96,151 +96,167 @@
   @include flexColumn($s-12);
 }
 
-.element-set-options-group {
-  &.open {
-    @include flexColumn;
-    .extended-options {
-      @include flexColumn;
-      .property-row {
-        @extend .attr-row;
-        &.big-row {
-          height: 100%;
-        }
-        .interaction-name {
-          @include twoLineTextEllipsis;
-          @include titleTipography;
-          padding-left: $s-4;
-          width: $s-92;
-          margin: auto 0;
-          grid-area: name;
-          color: var(--title-foreground-color);
-        }
-        .select-wrapper {
-          display: flex;
-          align-items: center;
-          grid-area: content;
-          .easing-select {
-            width: $s-156;
-            padding: 0 $s-8;
-            .dropdown-upwards {
-              bottom: $s-36;
-              width: $s-156;
-              top: unset;
-            }
-          }
-        }
-        .input-element-wrapper {
-          @extend .input-element;
-          grid-area: content;
-        }
-        .checkbox-option {
-          @extend .input-checkbox;
-          grid-area: content;
-        }
-        .position-btns-wrapper {
-          grid-area: content;
-          display: grid;
-          grid-template-areas:
-            "topleft top topright"
-            "left center right"
-            "bottomleft bottom bottomright";
-          grid-template-columns: repeat(3, 1fr);
-          grid-template-rows: repeat(3, 1fr);
-          width: $s-84;
-          height: $s-84;
-          border-radius: $br-8;
-          background-color: var(--color-background-tertiary);
-          .direction-btn {
-            @extend .button-tertiary;
-            height: $s-28;
-            width: $s-28;
-            .rectangle {
-              height: $s-8;
-              width: $s-8;
-              background-color: var(--color-background-quaternary);
-            }
-            &:hover {
-              .rectangle {
-                background-color: var(--color-accent-primary);
-              }
-            }
-            &.active {
-              background-color: var(--color-background-quaternary);
-              .rectangle {
-                background-color: var(--color-accent-primary);
-              }
-            }
-          }
-          .center-btn {
-            grid-area: center;
-          }
-          .top-left-btn {
-            grid-area: topleft;
-          }
-          .top-right-btn {
-            grid-area: topright;
-          }
-          .top-center-btn {
-            grid-area: top;
-          }
-          .bottom-left-btn {
-            grid-area: bottomleft;
-          }
-          .bottom-right-btn {
-            grid-area: bottomright;
-          }
-          .bottom-center-btn {
-            grid-area: bottom;
-          }
-        }
-        .buttons-wrapper {
-          grid-area: content;
-          .right svg {
-            transform: rotate(-90deg);
-          }
-          .left svg {
-            transform: rotate(90deg);
-          }
-          .up svg {
-            transform: rotate(180deg);
-          }
-        }
-        .inputs-wrapper {
-          grid-area: content;
-          @include flexRow;
-          .radio-btn {
-            @extend .input-checkbox;
-          }
-        }
+.element-set-options-group-open {
+  @include flexColumn;
+}
+
+.extended-options {
+  @include flexColumn;
+}
+
+.property-row {
+  @extend .attr-row;
+  &.big-row {
+    height: 100%;
+  }
+  .interaction-name {
+    @include twoLineTextEllipsis;
+    @include titleTipography;
+    padding-left: $s-4;
+    width: $s-92;
+    margin: auto 0;
+    grid-area: name;
+    color: var(--title-foreground-color);
+  }
+  .select-wrapper {
+    display: flex;
+    align-items: center;
+    grid-area: content;
+    .easing-select {
+      width: $s-156;
+      padding: 0 $s-8;
+      .dropdown-upwards {
+        bottom: $s-36;
+        width: $s-156;
+        top: unset;
       }
     }
   }
-
-  .interactions-summary {
-    @extend .asset-element;
-    height: $s-44;
-    padding: 0;
-    gap: $s-4;
-    .extend-btn {
+  .input-element-wrapper {
+    @extend .input-element;
+    grid-area: content;
+  }
+  .checkbox-option {
+    @extend .input-checkbox;
+    grid-area: content;
+  }
+  .position-btns-wrapper {
+    grid-area: content;
+    display: grid;
+    grid-template-areas:
+      "topleft top topright"
+      "left center right"
+      "bottomleft bottom bottomright";
+    grid-template-columns: repeat(3, 1fr);
+    grid-template-rows: repeat(3, 1fr);
+    width: $s-84;
+    height: $s-84;
+    border-radius: $br-8;
+    background-color: var(--color-background-tertiary);
+    .direction-btn {
       @extend .button-tertiary;
-      height: 100%;
+      height: $s-28;
       width: $s-28;
-      svg {
-        @extend .button-icon;
+      .rectangle {
+        height: $s-8;
+        width: $s-8;
+        background-color: var(--color-background-quaternary);
       }
-      &.extended {
-        @extend .button-icon-selected;
+      &:hover {
+        .rectangle {
+          background-color: var(--color-accent-primary);
+        }
+      }
+      &.active {
+        background-color: var(--color-background-quaternary);
+        .rectangle {
+          background-color: var(--color-accent-primary);
+        }
       }
     }
-
-    .remove-btn {
-      @extend .button-tertiary;
-      height: $s-32;
-      width: $s-28;
-      svg {
-        @extend .button-icon-small;
-      }
+    .center-btn {
+      grid-area: center;
     }
+    .top-left-btn {
+      grid-area: topleft;
+    }
+    .top-right-btn {
+      grid-area: topright;
+    }
+    .top-center-btn {
+      grid-area: top;
+    }
+    .bottom-left-btn {
+      grid-area: bottomleft;
+    }
+    .bottom-right-btn {
+      grid-area: bottomright;
+    }
+    .bottom-center-btn {
+      grid-area: bottom;
+    }
+  }
+  .buttons-wrapper {
+    grid-area: content;
+    .right svg {
+      transform: rotate(-90deg);
+    }
+    .left svg {
+      transform: rotate(90deg);
+    }
+    .up svg {
+      transform: rotate(180deg);
+    }
+  }
+  .inputs-wrapper {
+    grid-area: content;
+    @include flexRow;
+    .radio-btn {
+      @extend .input-checkbox;
+    }
+  }
+}
+
+.interactions-summary {
+  @extend .asset-element;
+  height: $s-44;
+  padding: 0;
+  gap: $s-8;
+
+  .remove-btn {
+    @extend .button-tertiary;
+    height: $s-32;
+    width: $s-28;
+    svg {
+      @extend .button-icon-small;
+    }
+  }
+}
+
+.extend-btn {
+  @extend .button-tertiary;
+  --button-tertiary-border-width: var(--expand-button-icon-border-width);
+  height: 100%;
+  width: $s-28;
+  border-end-end-radius: 0;
+  border-start-end-radius: 0;
+  padding: 0;
+  svg {
+    @extend .button-icon;
+  }
+  position: relative;
+  &:after {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    border-inline-end: $s-1 solid var(--panel-background-color);
+  }
+  &.extended {
+    @extend .button-icon-selected;
+    --button-tertiary-border-width: var(--expand-button-icon-border-width-selected);
   }
 }
 

From 4e152f470b44ff7224e092e1b0cba21687e969bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Fri, 9 Feb 2024 16:22:59 +0100
Subject: [PATCH 11/23] :bug: Fix overaly checkboxes in prototype tab

---
 .../sidebar/options/menus/interactions.cljs   |  8 ++++---
 .../sidebar/options/menus/interactions.scss   | 22 +++++++++++++++----
 2 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index f5f04bb7a..14b091461 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -573,7 +573,9 @@
               [:span {:class (stl/css :rectangle)}]]]]
 
                  ;; Overlay click outside
-           [:div {:class (stl/css :property-row)}
+
+          [:ul {:class (stl/css :property-list)}
+           [:li {:class (stl/css :property-row)}
             [:div {:class (stl/css :checkbox-option)}
              [:label {:for (str "close-" index)
                       :class (stl/css-case  :global/checked close-click-outside?)}
@@ -587,7 +589,7 @@
                        :on-change change-close-click-outside}]]]]
 
                 ;; Overlay background
-           [:div {:class (stl/css :property-row)}
+           [:li {:class (stl/css :property-row)}
             [:div {:class (stl/css :checkbox-option)}
              [:label {:for (str "background-" index)
                       :class (stl/css-case  :global/checked background-overlay?)}
@@ -598,7 +600,7 @@
               [:input {:type "checkbox"
                        :id (str "background-" index)
                        :checked background-overlay?
-                       :on-change change-background-overlay}]]]]])
+                       :on-change change-background-overlay}]]]]]])
 
         (when (ctsi/has-animation? interaction)
           [:*
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
index 09c1b4207..aa3989fe9 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
@@ -104,8 +104,17 @@
   @include flexColumn;
 }
 
+.property-list {
+  list-style: none;
+  margin: 0;
+  display: grid;
+  row-gap: $s-16;
+  margin-block: calc(#{$s-16} - #{$s-4});
+}
+
 .property-row {
   @extend .attr-row;
+  height: auto;
   &.big-row {
     height: 100%;
   }
@@ -136,10 +145,7 @@
     @extend .input-element;
     grid-area: content;
   }
-  .checkbox-option {
-    @extend .input-checkbox;
-    grid-area: content;
-  }
+
   .position-btns-wrapper {
     grid-area: content;
     display: grid;
@@ -216,6 +222,14 @@
     }
   }
 }
+.checkbox-option {
+  @extend .input-checkbox;
+  grid-area: content;
+  line-height: 1.2;
+  label {
+    align-items: start;
+  }
+}
 
 .interactions-summary {
   @extend .asset-element;

From 2633e56a76821f3c047e285b0140389ca0f72675 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Fri, 9 Feb 2024 16:25:40 +0100
Subject: [PATCH 12/23] :bug: Fix icon size in selects

---
 frontend/src/app/main/ui/components/select.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/frontend/src/app/main/ui/components/select.scss b/frontend/src/app/main/ui/components/select.scss
index 734fb3a40..e904c48ba 100644
--- a/frontend/src/app/main/ui/components/select.scss
+++ b/frontend/src/app/main/ui/components/select.scss
@@ -31,7 +31,7 @@
     width: $s-24;
     padding-right: $s-4;
     svg {
-      @extend .button-icon;
+      @extend .button-icon-small;
       stroke: var(--icon-foreground);
     }
   }

From c824711893fa2f7104440ee829b243603b04f86c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Fri, 9 Feb 2024 17:13:55 +0100
Subject: [PATCH 13/23] :bug: Replace overlay icons with new ones

---
 .../images/icons/corner-bottom-refactor.svg   |   3 +
 .../icons/corner-bottomleft-refactor.svg      |   3 +
 .../icons/corner-bottomright-refactor.svg     |   3 +
 .../images/icons/corner-center-refactor.svg   |   3 +
 .../images/icons/corner-top-refactor.svg      |   3 +
 .../images/icons/corner-topleft-refactor.svg  |   3 +
 .../images/icons/corner-topright-refactor.svg |   3 +
 .../styles/common/refactor/basic-rules.scss   |   2 +-
 frontend/src/app/main/ui/icons.cljs           |   7 ++
 .../sidebar/options/menus/interactions.cljs   |  95 +++++++--------
 .../sidebar/options/menus/interactions.scss   | 111 +++++++++---------
 11 files changed, 132 insertions(+), 104 deletions(-)
 create mode 100644 frontend/resources/images/icons/corner-bottom-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-bottomleft-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-bottomright-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-center-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-top-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-topleft-refactor.svg
 create mode 100644 frontend/resources/images/icons/corner-topright-refactor.svg

diff --git a/frontend/resources/images/icons/corner-bottom-refactor.svg b/frontend/resources/images/icons/corner-bottom-refactor.svg
new file mode 100644
index 000000000..ca2c80ea5
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottom-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M15.333 14H.667"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-bottomleft-refactor.svg b/frontend/resources/images/icons/corner-bottomleft-refactor.svg
new file mode 100644
index 000000000..ed9972420
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottomleft-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M14 14H9.5A7.5 7.5 0 012 6.5V2"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-bottomright-refactor.svg b/frontend/resources/images/icons/corner-bottomright-refactor.svg
new file mode 100644
index 000000000..8292d9bbc
--- /dev/null
+++ b/frontend/resources/images/icons/corner-bottomright-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M2 14h4.5A7.5 7.5 0 0014 6.5V2"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-center-refactor.svg b/frontend/resources/images/icons/corner-center-refactor.svg
new file mode 100644
index 000000000..72b387bc0
--- /dev/null
+++ b/frontend/resources/images/icons/corner-center-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M.667 8h14.666"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-top-refactor.svg b/frontend/resources/images/icons/corner-top-refactor.svg
new file mode 100644
index 000000000..542f48376
--- /dev/null
+++ b/frontend/resources/images/icons/corner-top-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M15.333 2H.667"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-topleft-refactor.svg b/frontend/resources/images/icons/corner-topleft-refactor.svg
new file mode 100644
index 000000000..eeabf0c23
--- /dev/null
+++ b/frontend/resources/images/icons/corner-topleft-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M14 2H9.5A7.499 7.499 0 002 9.5V14"/>
+</svg>
diff --git a/frontend/resources/images/icons/corner-topright-refactor.svg b/frontend/resources/images/icons/corner-topright-refactor.svg
new file mode 100644
index 000000000..27454f142
--- /dev/null
+++ b/frontend/resources/images/icons/corner-topright-refactor.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M2 2h4.5A7.499 7.499 0 0114 9.5V14"/>
+</svg>
diff --git a/frontend/resources/styles/common/refactor/basic-rules.scss b/frontend/resources/styles/common/refactor/basic-rules.scss
index 83f2c904c..4dd70f856 100644
--- a/frontend/resources/styles/common/refactor/basic-rules.scss
+++ b/frontend/resources/styles/common/refactor/basic-rules.scss
@@ -125,7 +125,7 @@
   @include buttonStyle;
   @include flexCenter;
   @include focusTertiary;
-  --button-tertiary-border-width: $s-2;
+  --button-tertiary-border-width: #{$s-2};
   border-radius: $br-8;
   color: var(--button-tertiary-foreground-color-rest);
   background-color: transparent;
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index f65ff1676..edcb1016b 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -322,6 +322,13 @@
 (def ^:icon column-reverse-refactor (icon-xref :column-reverse-refactor))
 (def ^:icon constraint-horizontal-refactor (icon-xref :constraint-horizontal-refactor))
 (def ^:icon constraint-vertical-refactor (icon-xref :constraint-vertical-refactor))
+(def ^:icon corner-bottom-refactor (icon-xref :corner-bottom-refactor))
+(def ^:icon corner-bottomleft-refactor (icon-xref :corner-bottomleft-refactor))
+(def ^:icon corner-bottomright-refactor (icon-xref :corner-bottom-refactor))
+(def ^:icon corner-center-refactor (icon-xref :corner-center-refactor))
+(def ^:icon corner-top-refactor (icon-xref :corner-top-refactor))
+(def ^:icon corner-topleft-refactor (icon-xref :corner-topleft-refactor))
+(def ^:icon corner-topright-refactor (icon-xref :corner-topright-refactor))
 (def ^:icon corner-radius-refactor (icon-xref :corner-radius-refactor))
 (def ^:icon curve-refactor (icon-xref :curve-refactor))
 (def ^:icon distribute-vertical-spacing-refactor (icon-xref :distribute-vertical-spacing-refactor))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index 14b091461..766a6b3c0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -182,6 +182,21 @@
        (when flow
          [:& flow-item {:flow flow :key (str (:id flow))}])])))
 
+(def ^:private corner-center-icon
+  (i/icon-xref :corner-center-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottom-icon
+  (i/icon-xref :corner-bottom-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottomleft-icon
+  (i/icon-xref :corner-bottomleft-refactor (stl/css :corner-icon)))
+(def ^:private corner-bottomright-icon
+  (i/icon-xref :corner-bottomright-refactor (stl/css :corner-icon)))
+(def ^:private corner-top-icon
+  (i/icon-xref :corner-top-refactor (stl/css :corner-icon)))
+(def ^:private corner-topleft-icon
+  (i/icon-xref :corner-topleft-refactor (stl/css :corner-icon)))
+(def ^:private corner-topright-icon
+  (i/icon-xref :corner-topright-refactor (stl/css :corner-icon)))
+
 (mf/defc interaction-entry
   [{:keys [index shape interaction update-interaction remove-interaction]}]
   (let [objects              (deref refs/workspace-page-objects)
@@ -407,8 +422,8 @@
           ; Summary
      [:div {:class (stl/css :interactions-summary)}
       [:button {:class (stl/css-case :extend-btn true
-                                  :extended extended-open?)
-             :on-click toggle-extended}
+                                     :extended extended-open?)
+                :on-click toggle-extended}
        i/menu-refactor]
 
       [:div {:class (stl/css :interactions-info)
@@ -520,87 +535,75 @@
                                             :active (= overlay-pos-type :center))
                        :data-value "center"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-center-icon]
              [:button {:class (stl/css-case :direction-btn true
                                             :top-left-btn true
                                             :active (= overlay-pos-type :top-left))
                        :data-value "top-left"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-topleft-icon]
              [:button {:class (stl/css-case :direction-btn true
                                             :top-right-btn true
                                             :active (= overlay-pos-type :top-right))
                        :data-value "top-right"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-topright-icon]
 
              [:button {:class (stl/css-case :direction-btn true
                                             :top-center-btn true
                                             :active (= overlay-pos-type :top-center))
                        :data-value "top-center"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
-             [:button {:class (stl/css-case :direction-btn true
-                                            :bottom-left-btn true
-                                            :active (= overlay-pos-type :bottom-left))
-                       :data-value "bottom-left"
-                       :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
-             [:button {:class (stl/css-case :direction-btn true
-                                            :bottom-left-btn true
-                                            :active (= overlay-pos-type :bottom-left))
-                       :data-value "bottom-left"
-                       :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-top-icon]
 
              [:button {:class (stl/css-case :direction-btn true
                                             :bottom-left-btn true
                                             :active (= overlay-pos-type :bottom-left))
                        :data-value "bottom-left"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-bottomleft-icon]
              [:button {:class (stl/css-case :direction-btn true
                                             :bottom-right-btn true
                                             :active (= overlay-pos-type :bottom-right))
                        :data-value "bottom-right"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]
+              corner-bottomright-icon]
              [:button {:class (stl/css-case :direction-btn true
                                             :bottom-center-btn true
                                             :active (= overlay-pos-type :bottom-center))
                        :data-value "bottom-center"
                        :on-click toggle-overlay-pos-type}
-              [:span {:class (stl/css :rectangle)}]]]]
+              corner-bottom-icon]]]
 
                  ;; Overlay click outside
 
-          [:ul {:class (stl/css :property-list)}
-           [:li {:class (stl/css :property-row)}
-            [:div {:class (stl/css :checkbox-option)}
-             [:label {:for (str "close-" index)
-                      :class (stl/css-case  :global/checked close-click-outside?)}
-              [:span {:class (stl/css-case :global/checked close-click-outside?)}
-               (when close-click-outside?
-                 i/status-tick-refactor)]
-              (tr "workspace.options.interaction-close-outside")
-              [:input {:type "checkbox"
-                       :id (str "close-" index)
-                       :checked close-click-outside?
-                       :on-change change-close-click-outside}]]]]
+           [:ul {:class (stl/css :property-list)}
+            [:li {:class (stl/css :property-row)}
+             [:div {:class (stl/css :checkbox-option)}
+              [:label {:for (str "close-" index)
+                       :class (stl/css-case  :global/checked close-click-outside?)}
+               [:span {:class (stl/css-case :global/checked close-click-outside?)}
+                (when close-click-outside?
+                  i/status-tick-refactor)]
+               (tr "workspace.options.interaction-close-outside")
+               [:input {:type "checkbox"
+                        :id (str "close-" index)
+                        :checked close-click-outside?
+                        :on-change change-close-click-outside}]]]]
 
                 ;; Overlay background
-           [:li {:class (stl/css :property-row)}
-            [:div {:class (stl/css :checkbox-option)}
-             [:label {:for (str "background-" index)
-                      :class (stl/css-case  :global/checked background-overlay?)}
-              [:span {:class (stl/css-case :global/checked background-overlay?)}
-               (when background-overlay?
-                 i/status-tick-refactor)]
-              (tr "workspace.options.interaction-background")
-              [:input {:type "checkbox"
-                       :id (str "background-" index)
-                       :checked background-overlay?
-                       :on-change change-background-overlay}]]]]]])
+            [:li {:class (stl/css :property-row)}
+             [:div {:class (stl/css :checkbox-option)}
+              [:label {:for (str "background-" index)
+                       :class (stl/css-case  :global/checked background-overlay?)}
+               [:span {:class (stl/css-case :global/checked background-overlay?)}
+                (when background-overlay?
+                  i/status-tick-refactor)]
+               (tr "workspace.options.interaction-background")
+               [:input {:type "checkbox"
+                        :id (str "background-" index)
+                        :checked background-overlay?
+                        :on-change change-background-overlay}]]]]]])
 
         (when (ctsi/has-animation? interaction)
           [:*
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
index aa3989fe9..f9eb79f54 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.scss
@@ -145,63 +145,6 @@
     @extend .input-element;
     grid-area: content;
   }
-
-  .position-btns-wrapper {
-    grid-area: content;
-    display: grid;
-    grid-template-areas:
-      "topleft top topright"
-      "left center right"
-      "bottomleft bottom bottomright";
-    grid-template-columns: repeat(3, 1fr);
-    grid-template-rows: repeat(3, 1fr);
-    width: $s-84;
-    height: $s-84;
-    border-radius: $br-8;
-    background-color: var(--color-background-tertiary);
-    .direction-btn {
-      @extend .button-tertiary;
-      height: $s-28;
-      width: $s-28;
-      .rectangle {
-        height: $s-8;
-        width: $s-8;
-        background-color: var(--color-background-quaternary);
-      }
-      &:hover {
-        .rectangle {
-          background-color: var(--color-accent-primary);
-        }
-      }
-      &.active {
-        background-color: var(--color-background-quaternary);
-        .rectangle {
-          background-color: var(--color-accent-primary);
-        }
-      }
-    }
-    .center-btn {
-      grid-area: center;
-    }
-    .top-left-btn {
-      grid-area: topleft;
-    }
-    .top-right-btn {
-      grid-area: topright;
-    }
-    .top-center-btn {
-      grid-area: top;
-    }
-    .bottom-left-btn {
-      grid-area: bottomleft;
-    }
-    .bottom-right-btn {
-      grid-area: bottomright;
-    }
-    .bottom-center-btn {
-      grid-area: bottom;
-    }
-  }
   .buttons-wrapper {
     grid-area: content;
     .right svg {
@@ -222,6 +165,53 @@
     }
   }
 }
+
+.position-btns-wrapper {
+  grid-area: content;
+  display: grid;
+  grid-template-areas:
+    "topleft top topright"
+    "left center right"
+    "bottomleft bottom bottomright";
+  grid-template-columns: repeat(3, 1fr);
+  grid-template-rows: repeat(3, 1fr);
+  width: $s-84;
+  height: $s-84;
+  border-radius: $br-8;
+  background-color: var(--color-background-tertiary);
+  .center-btn {
+    grid-area: center;
+  }
+  .top-left-btn {
+    grid-area: topleft;
+  }
+  .top-right-btn {
+    grid-area: topright;
+  }
+  .top-center-btn {
+    grid-area: top;
+  }
+  .bottom-left-btn {
+    grid-area: bottomleft;
+  }
+  .bottom-right-btn {
+    grid-area: bottomright;
+  }
+  .bottom-center-btn {
+    grid-area: bottom;
+  }
+}
+
+.direction-btn {
+  @extend .button-tertiary;
+  height: $s-28;
+  width: $s-28;
+
+  &.active {
+    @extend .button-icon-selected;
+  }
+}
+
 .checkbox-option {
   @extend .input-checkbox;
   grid-area: content;
@@ -274,6 +264,13 @@
   }
 }
 
+.corner-icon {
+  fill: none;
+  stroke: currentColor;
+  width: $s-12;
+  height: $s-12;
+}
+
 .flow-element {
   @include flexRow;
 }

From 1415ed30b6e52c53e121f6714bee1c13c654d0dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Tue, 13 Feb 2024 15:23:07 +0100
Subject: [PATCH 14/23] :bug: Fix icon not being shown when asset category had
 a zero count

---
 .../src/app/main/ui/components/title_bar.cljs |  4 +--
 .../src/app/main/ui/components/title_bar.scss | 35 +++++++++++--------
 .../ui/workspace/sidebar/assets/common.cljs   |  1 +
 .../ui/workspace/sidebar/assets/common.scss   |  2 +-
 4 files changed, 25 insertions(+), 17 deletions(-)

diff --git a/frontend/src/app/main/ui/components/title_bar.cljs b/frontend/src/app/main/ui/components/title_bar.cljs
index 61c664303..0c9899bbc 100644
--- a/frontend/src/app/main/ui/components/title_bar.cljs
+++ b/frontend/src/app/main/ui/components/title_bar.cljs
@@ -13,7 +13,7 @@
 
 (mf/defc title-bar
   {::mf/wrap-props false}
-  [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable]}]
+  [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable add-icon-gap]}]
   (let [klass (dm/str (stl/css :title-bar) " " class)]
     [:div {:class klass}
      (if ^boolean collapsable
@@ -33,7 +33,7 @@
                      :on-click on-collapsed}
             i/arrow-refactor]
            [:div {:class (stl/css :title)} title]])]
-       [:div {:class (stl/css :title-only)} title])
+       [:div {:class (stl/css-case :title-only true :title-only-icon-gap add-icon-gap)} title])
      children
      (when (some? on-btn-click)
        [:button {:class (stl/css :title-button)
diff --git a/frontend/src/app/main/ui/components/title_bar.scss b/frontend/src/app/main/ui/components/title_bar.scss
index 2ec13406b..e76b9fe2b 100644
--- a/frontend/src/app/main/ui/components/title_bar.scss
+++ b/frontend/src/app/main/ui/components/title_bar.scss
@@ -14,20 +14,6 @@
   width: 100%;
   min-height: $s-32;
   background-color: var(--title-background-color);
-  .title,
-  .title-only {
-    @include tabTitleTipography;
-    display: flex;
-    align-items: center;
-    flex-grow: 1;
-    height: 100%;
-    min-height: $s-32;
-    color: var(--title-foreground-color);
-    overflow: hidden;
-  }
-  .title-only {
-    margin-left: $s-8;
-  }
 
   .title-wrapper {
     display: flex;
@@ -109,3 +95,24 @@
     }
   }
 }
+
+.title,
+.title-only {
+  @include tabTitleTipography;
+  display: flex;
+  align-items: center;
+  flex-grow: 1;
+  height: 100%;
+  min-height: $s-32;
+  color: var(--title-foreground-color);
+  overflow: hidden;
+}
+
+.title-only {
+  --title-bar-title-margin: #{$s-8};
+  margin-inline-start: var(--title-bar-title-margin);
+}
+
+.title-only-icon-gap {
+  --title-bar-title-margin: #{$s-12};
+}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
index d2f3ed1db..3ef6fb6ad 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs
@@ -166,6 +166,7 @@
        :collapsed     (not open?)
        :all-clickable true
        :on-collapsed  on-collapsed
+       :add-icon-gap  (= 0 assets-count)
        :class         (stl/css-case :title-spacing open?)
        :title         title}
       buttons]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss b/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
index 84d86bdc8..063cfa831 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.scss
@@ -21,8 +21,8 @@
     @include flexCenter;
     height: $s-16;
     width: $s-16;
-    color: transparent;
     fill: none;
+    stroke: currentColor;
   }
 }
 

From bc3d268f57a7c9a6b4699341f557c64a028b1816 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 18:40:16 +0100
Subject: [PATCH 15/23] :sparkles: Add minor improvements to srepl helpers

---
 backend/src/app/srepl/fixes.clj |  7 ++++++
 backend/src/app/srepl/main.clj  | 42 ++++++---------------------------
 2 files changed, 14 insertions(+), 35 deletions(-)

diff --git a/backend/src/app/srepl/fixes.clj b/backend/src/app/srepl/fixes.clj
index 5e450e0d9..c5ba162f0 100644
--- a/backend/src/app/srepl/fixes.clj
+++ b/backend/src/app/srepl/fixes.clj
@@ -29,6 +29,13 @@
         (update :data feat.fdata/process-objects (partial into {}))
         (update :features disj "fdata/pointer-map" "fdata/objects-map"))))
 
+(def sql:get-fdata-files
+  "SELECT id FROM file
+    WHERE deleted_at is NULL
+      AND (features @> '{fdata/pointer-map}' OR
+           features @> '{fdata/objects-map}')
+    ORDER BY created_at DESC")
+
 (defn find-fdata-pointers
   [{:keys [id features data] :as file} _]
   (when (contains? features "fdata/pointer-map")
diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj
index 9851af8a3..ba9693e93 100644
--- a/backend/src/app/srepl/main.clj
+++ b/backend/src/app/srepl/main.clj
@@ -370,40 +370,11 @@
 ;; PROCESSING
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(def ^:private
-  sql:get-file-ids
+(def sql:get-files
   "SELECT id FROM file
-    WHERE created_at < ? AND deleted_at is NULL
+    WHERE deleted_at is NULL
     ORDER BY created_at DESC")
 
-(defn analyze-files
-  "Apply a function to all files in the database, reading them in
-  batches. Do not change data.
-
-  Emits rollback at the end of operation."
-  [on-file & {:keys [max-items start-at with-libraries?]}]
-  (letfn [(get-candidates [conn]
-            (cond->> (db/cursor conn [sql:get-file-ids (or start-at (dt/now))])
-              (some? max-items)
-              (take max-items)))
-
-          (process-file [{:keys [::db/conn] :as system} file-id]
-            (let [file (h/get-file system file-id)
-                  libs (when with-libraries?
-                         (->> (files/get-file-libraries conn file-id)
-                              (into [file] (map (fn [{:keys [id]}]
-                                                  (h/get-file system id))))
-                              (d/index-by :id)))]
-              (if with-libraries?
-                (on-file file libs)
-                (on-file file))))]
-
-    (db/tx-run! (assoc main/system ::db/rollback true)
-                (fn [{:keys [::db/conn] :as system}]
-                  (binding [h/*system* system]
-                    (run! (partial process-file system)
-                          (get-candidates conn)))))))
-
 (defn process-file!
   "Apply a function to the file. Optionally save the changes or not.
   The function receives the decoded and migrated file data."
@@ -435,11 +406,12 @@
   "Apply a function to all files in the database"
   [update-fn & {:keys [max-items
                        max-jobs
-                       start-at
-                       rollback?]
+                       rollback?
+                       query]
                 :or {max-jobs 1
                      max-items Long/MAX_VALUE
-                     rollback? true}
+                     rollback? true
+                     query sql:get-files}
                 :as opts}]
 
   (l/dbg :hint "process:start"
@@ -483,7 +455,7 @@
                       (px/run! executor (partial process-file file-id idx (dt/tpoint)))
                       (inc idx))
                     0
-                    (->> (db/cursor conn [sql:get-file-ids (or start-at (dt/now))])
+                    (->> (db/cursor conn [query] {:chunk-size 1})
                          (take max-items)
                          (map :id)))
             (finally

From afd68fa09d20ba56a479223c89aba567d797dada Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 17:34:32 +0100
Subject: [PATCH 16/23] :bug: Properly handle fdata features on file-gc task

It also adds a schema validation process after cleaning. If file
does not validates it will be skiped.
---
 backend/src/app/binfile/common.clj            |   3 -
 backend/src/app/tasks/file_gc.clj             | 474 ++++++++++--------
 backend/test/backend_tests/rpc_file_test.clj  |  50 +-
 .../rpc_file_thumbnails_test.clj              |   2 +-
 4 files changed, 284 insertions(+), 245 deletions(-)

diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj
index aceb4ef7b..4f9af0e7f 100644
--- a/backend/src/app/binfile/common.clj
+++ b/backend/src/app/binfile/common.clj
@@ -141,8 +141,6 @@
                                 " WHERE flr.file_id = ANY(?) AND l.deleted_at IS NULL")]
                    (db/exec! conn [sql ids])))))
 
-
-;; NOTE: Will be used in future, commented for satisfy linter
 (def ^:private sql:get-libraries
   "WITH RECURSIVE libs AS (
      SELECT fl.id
@@ -409,7 +407,6 @@
                           (update :colors relink-colors)
                           (d/without-nils))))))
 
-
 (defn- upsert-file!
   [conn file]
   (let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) "
diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj
index b5b9e4dd9..031bf357a 100644
--- a/backend/src/app/tasks/file_gc.clj
+++ b/backend/src/app/tasks/file_gc.clj
@@ -11,7 +11,8 @@
   inactivity (the default threshold is 72h)."
   (:require
    [app.binfile.common :as bfc]
-   [app.common.files.migrations :as pmg]
+   [app.common.files.migrations :as fmg]
+   [app.common.files.validate :as cfv]
    [app.common.logging :as l]
    [app.common.thumbnails :as thc]
    [app.common.types.components-list :as ctkl]
@@ -29,9 +30,256 @@
    [clojure.spec.alpha :as s]
    [integrant.core :as ig]))
 
-(declare ^:private get-candidates)
 (declare ^:private clean-file!)
 
+(defn- decode-file
+  [cfg {:keys [id] :as file}]
+  (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
+    (-> file
+        (update :features db/decode-pgarray #{})
+        (update :data blob/decode)
+        (update :data feat.fdata/process-pointers deref)
+        (update :data feat.fdata/process-objects (partial into {}))
+        (update :data assoc :id id)
+        (fmg/migrate-file))))
+
+(defn- update-file!
+  [{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
+  (let [file (if (contains? (:features file) "fdata/objects-map")
+               (feat.fdata/enable-objects-map file)
+               file)
+
+        file (if (contains? (:features file) "fdata/pointer-map")
+               (binding [pmap/*tracked* (pmap/create-tracked)]
+                 (let [file (feat.fdata/enable-pointer-map file)]
+                   (feat.fdata/persist-pointers! cfg id)
+                   file))
+               file)
+
+        file (-> file
+                 (update :features db/encode-pgarray conn "text")
+                 (update :data blob/encode))]
+
+    (db/update! conn :file
+                {:has-media-trimmed true
+                 :data (:data file)}
+                {:id id})))
+
+(defn- process-file!
+  [cfg file]
+  (try
+    (let [file (decode-file cfg file)
+          file (clean-file! cfg file)]
+      (cfv/validate-file-schema! file)
+      (update-file! cfg file))
+    (catch Throwable cause
+      (l/err :hint "error on cleaning file (skiping)"
+             :file-id (str (:id file))
+             :cause cause))))
+
+(def ^:private
+  sql:get-candidates
+  "SELECT f.id,
+          f.data,
+          f.revn,
+          f.features,
+          f.modified_at
+     FROM file AS f
+    WHERE f.has_media_trimmed IS false
+      AND f.modified_at < now() - ?::interval
+    ORDER BY f.modified_at DESC
+      FOR UPDATE
+     SKIP LOCKED")
+
+(defn- get-candidates
+  [{:keys [::db/conn ::min-age ::file-id]}]
+  (if (uuid? file-id)
+    (do
+      (l/warn :hint "explicit file id passed on params" :file-id (str file-id))
+      (db/query conn :file {:id file-id}))
+
+    (let [min-age (db/interval min-age)]
+      (db/cursor conn [sql:get-candidates min-age] {:chunk-size 1}))))
+
+(def ^:private sql:mark-file-media-object-deleted
+  "UPDATE file_media_object
+      SET deleted_at = now()
+    WHERE file_id = ? AND id != ALL(?::uuid[])
+   RETURNING id")
+
+(defn- clean-file-media!
+  "Performs the garbage collection of file media objects."
+  [{:keys [::db/conn]} {:keys [id data] :as file}]
+  (let [used   (bfc/collect-used-media data)
+        ids    (db/create-array conn "uuid" used)
+        unused (->> (db/exec! conn [sql:mark-file-media-object-deleted id ids])
+                    (into #{} (map :id)))]
+
+    (doseq [id unused]
+      (l/trc :hint "mark deleted"
+             :rel "file-media-object"
+             :id (str id)
+             :file-id (str id)))
+
+    [(count unused) file]))
+
+(def ^:private sql:mark-file-object-thumbnails-deleted
+  "UPDATE file_tagged_object_thumbnail
+      SET deleted_at = now()
+    WHERE file_id = ? AND object_id != ALL(?::text[])
+   RETURNING object_id")
+
+(defn- clean-file-object-thumbnails!
+  [{:keys [::db/conn]} {:keys [data] :as file}]
+  (let [file-id (:id file)
+        using   (->> (vals (:pages-index data))
+                     (into #{} (comp
+                                (mapcat (fn [{:keys [id objects]}]
+                                          (->> (ctt/get-frames objects)
+                                               (map #(assoc % :page-id id)))))
+                                (mapcat (fn [{:keys [id page-id]}]
+                                          (list
+                                           (thc/fmt-object-id file-id page-id id "frame")
+                                           (thc/fmt-object-id file-id page-id id "component")))))))
+
+        ids    (db/create-array conn "text" using)
+        unused (->> (db/exec! conn [sql:mark-file-object-thumbnails-deleted file-id ids])
+                    (into #{} (map :object-id)))]
+
+    (doseq [object-id unused]
+      (l/trc :hint "mark deleted"
+             :rel "file-tagged-object-thumbnail"
+             :object-id object-id
+             :file-id (str file-id)))
+
+    [(count unused) file]))
+
+(def ^:private sql:mark-file-thumbnails-deleted
+  "UPDATE file_thumbnail
+      SET deleted_at = now()
+    WHERE file_id = ? AND revn < ?
+   RETURNING revn")
+
+(defn- clean-file-thumbnails!
+  [{:keys [::db/conn]} {:keys [id revn] :as file}]
+  (let [unused (->> (db/exec! conn [sql:mark-file-thumbnails-deleted id revn])
+                    (into #{} (map :revn)))]
+
+    (doseq [revn unused]
+      (l/trc :hint "mark deleted"
+             :rel "file-thumbnail"
+             :revn revn
+             :file-id (str id)))
+
+    [(count unused) file]))
+
+
+(def ^:private sql:get-files-for-library
+  "SELECT f.id, f.data, f.modified_at, f.features
+     FROM file AS f
+     LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id)
+    WHERE fl.library_file_id = ?
+      AND f.deleted_at IS null
+    ORDER BY f.modified_at ASC")
+
+(defn- clean-deleted-components!
+  "Performs the garbage collection of unreferenced deleted components."
+  [{:keys [::db/conn] :as cfg} {:keys [data] :as file}]
+  (let [file-id (:id file)
+
+        get-used-components
+        (fn [data components]
+          ;; Find which of the components are used in the file.
+          (into #{}
+                (filter #(ctf/used-in? data file-id % :component))
+                components))
+
+        get-unused-components
+        (fn [components files]
+          ;; Find and return a set of unused components (on all files).
+          (reduce (fn [components {:keys [data]}]
+                    (if (seq components)
+                      (->> (get-used-components data components)
+                           (set/difference components))
+                      (reduced components)))
+
+                  components
+                  files))
+
+        process-fdata
+        (fn [data unused]
+          (reduce (fn [data id]
+                    (l/trc :hint "delete component"
+                           :component-id (str id)
+                           :file-id (str file-id))
+                    (ctkl/delete-component data id))
+                  data
+                  unused))
+
+        deleted (into #{} (ctkl/deleted-components-seq data))
+
+        unused  (->> (db/cursor conn [sql:get-files-for-library file-id] {:chunk-size 1})
+                     (map (partial decode-file cfg))
+                     (cons file)
+                     (get-unused-components deleted)
+                     (mapv :id)
+                     (set))
+
+        file    (update file :data process-fdata unused)]
+
+    [(count unused) file]))
+
+(def ^:private sql:get-changes
+  "SELECT id, data FROM file_change
+    WHERE file_id = ? AND data IS NOT NULL
+    ORDER BY created_at ASC")
+
+(def ^:private sql:mark-deleted-data-fragments
+  "UPDATE file_data_fragment
+      SET deleted_at = now()
+    WHERE file_id = ?
+      AND id != ALL(?::uuid[])
+   RETURNING id")
+
+(defn- clean-data-fragments!
+  [{:keys [::db/conn]} {:keys [id data] :as file}]
+  (let [used   (->> (db/cursor conn [sql:get-changes id])
+                    (into (feat.fdata/get-used-pointer-ids data)
+                          (comp (map :data)
+                                (map blob/decode)
+                                (mapcat feat.fdata/get-used-pointer-ids))))
+
+        unused (let [ids (db/create-array conn "uuid" used)]
+                 (->> (db/exec! conn [sql:mark-deleted-data-fragments id ids])
+                      (into #{} (map :id))))]
+
+    (doseq [id unused]
+      (l/trc :hint "mark deleted"
+             :rel "file-data-fragment"
+             :id (str id)
+             :file-id (str id)))
+
+    [(count unused) file]))
+
+(defn- clean-file!
+  [cfg {:keys [id] :as file}]
+  (let [[n1 file] (clean-file-media! cfg file)
+        [n2 file] (clean-file-thumbnails! cfg file)
+        [n3 file] (clean-file-object-thumbnails! cfg file)
+        [n4 file] (clean-deleted-components! cfg file)
+        [n5 file] (clean-data-fragments! cfg file)]
+
+    (l/dbg :hint "file clened"
+           :file-id (str id)
+           :modified-at (dt/format-instant (:modified-at file))
+           :media-objects n1
+           :thumbnails n2
+           :object-thumbnails n3
+           :components n4
+           :data-fragments n5)
+
+    file))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; HANDLER
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -55,7 +303,7 @@
                                     (assoc ::min-age min-age))
 
                         total   (reduce (fn [total file]
-                                          (clean-file! cfg file)
+                                          (process-file! cfg file)
                                           (inc total))
                                         0
                                         (get-candidates cfg))]
@@ -69,223 +317,3 @@
                       (db/rollback! conn))
 
                     {:processed total})))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; IMPL
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
-(def ^:private
-  sql:get-candidates
-  "SELECT f.id,
-          f.data,
-          f.revn,
-          f.features,
-          f.modified_at
-     FROM file AS f
-    WHERE f.has_media_trimmed IS false
-      AND f.modified_at < now() - ?::interval
-    ORDER BY f.modified_at DESC
-      FOR UPDATE
-     SKIP LOCKED")
-
-(defn- get-candidates
-  [{:keys [::db/conn ::min-age ::file-id]}]
-  (if (uuid? file-id)
-    (do
-      (l/warn :hint "explicit file id passed on params" :file-id (str file-id))
-      (->> (db/query conn :file {:id file-id})
-           (map #(update % :features db/decode-pgarray #{}))))
-
-    (let [min-age (db/interval min-age)]
-      (->> (db/cursor conn [sql:get-candidates min-age] {:chunk-size 1})
-           (map #(update % :features db/decode-pgarray #{}))))))
-
-(def ^:private sql:mark-file-media-object-deleted
-  "UPDATE file_media_object
-      SET deleted_at = now()
-    WHERE file_id = ? AND id != ALL(?::uuid[])
-   RETURNING id")
-
-(defn- clean-file-media!
-  "Performs the garbage collection of file media objects."
-  [conn file-id data]
-  (let [used   (bfc/collect-used-media data)
-        ids    (db/create-array conn "uuid" used)
-        unused (->> (db/exec! conn [sql:mark-file-media-object-deleted file-id ids])
-                    (into #{} (map :id)))]
-
-    (doseq [id unused]
-      (l/trc :hint "mark deleted"
-             :rel "file-media-object"
-             :id (str id)
-             :file-id (str file-id)))
-
-    (count unused)))
-
-
-(def ^:private sql:mark-file-object-thumbnails-deleted
-  "UPDATE file_tagged_object_thumbnail
-      SET deleted_at = now()
-    WHERE file_id = ? AND object_id != ALL(?::text[])
-   RETURNING object_id")
-
-(defn- clean-file-object-thumbnails!
-  [{:keys [::db/conn]} file-id data]
-  (let [using  (->> (vals (:pages-index data))
-                    (into #{} (comp
-                               (mapcat (fn [{:keys [id objects]}]
-                                         (->> (ctt/get-frames objects)
-                                              (map #(assoc % :page-id id)))))
-                               (mapcat (fn [{:keys [id page-id]}]
-                                         (list
-                                          (thc/fmt-object-id file-id page-id id "frame")
-                                          (thc/fmt-object-id file-id page-id id "component")))))))
-
-        ids    (db/create-array conn "text" using)
-        unused (->> (db/exec! conn [sql:mark-file-object-thumbnails-deleted file-id ids])
-                    (into #{} (map :object-id)))]
-
-    (doseq [object-id unused]
-      (l/trc :hint "mark deleted"
-             :rel "file-tagged-object-thumbnail"
-             :object-id object-id
-             :file-id (str file-id)))
-
-    (count unused)))
-
-
-(def ^:private sql:mark-file-thumbnails-deleted
-  "UPDATE file_thumbnail
-      SET deleted_at = now()
-    WHERE file_id = ? AND revn < ?
-   RETURNING revn")
-
-(defn- clean-file-thumbnails!
-  [{:keys [::db/conn]} file-id revn]
-  (let [unused (->> (db/exec! conn [sql:mark-file-thumbnails-deleted file-id revn])
-                    (into #{} (map :revn)))]
-
-    (doseq [revn unused]
-      (l/trc :hint "mark deleted"
-             :rel "file-thumbnail"
-             :revn revn
-             :file-id (str file-id)))
-
-    (count unused)))
-
-
-(def ^:private sql:get-files-for-library
-  "SELECT f.id, f.data, f.modified_at
-     FROM file AS f
-     LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id)
-    WHERE fl.library_file_id = ?
-      AND f.deleted_at IS null
-    ORDER BY f.modified_at ASC")
-
-(defn- clean-deleted-components!
-  "Performs the garbage collection of unreferenced deleted components."
-  [{:keys [::db/conn] :as cfg} file-id data]
-  (letfn [(get-used-components [fdata components]
-            ;; Find which of the components are used in the file.
-            (into #{}
-                  (filter #(ctf/used-in? fdata file-id % :component))
-                  components))
-
-          (get-unused-components [components files-data]
-            ;; Find and return a set of unused components (on all files).
-            (reduce (fn [components fdata]
-                      (if (seq components)
-                        (->> (get-used-components fdata components)
-                             (set/difference components))
-                        (reduced components)))
-
-                    components
-                    files-data))]
-
-    (let [deleted (into #{} (ctkl/deleted-components-seq data))
-          unused  (->> (db/cursor conn [sql:get-files-for-library file-id] {:chunk-size 1})
-                       (map (fn [{:keys [id data] :as file}]
-                              (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
-                                (-> (blob/decode data)
-                                    (feat.fdata/process-pointers deref)))))
-                       (cons data)
-                       (get-unused-components deleted)
-                       (mapv :id))]
-
-      (doseq [id unused]
-        (l/trc :hint "delete component" :component-id (str id) :file-id (str file-id)))
-
-
-      (when-let [data (some->> (seq unused)
-                               (reduce ctkl/delete-component data)
-                               (blob/encode))]
-        (db/update! conn :file
-                    {:data data}
-                    {:id file-id}))
-
-      (count unused))))
-
-
-(def ^:private sql:get-changes
-  "SELECT id, data FROM file_change
-    WHERE file_id = ? AND data IS NOT NULL
-    ORDER BY created_at ASC")
-
-(def ^:private sql:mark-deleted-data-fragments
-  "UPDATE file_data_fragment
-      SET deleted_at = now()
-    WHERE file_id = ?
-      AND id != ALL(?::uuid[])
-   RETURNING id")
-
-(defn- clean-data-fragments!
-  [conn file-id data]
-  (let [used   (->> (db/cursor conn [sql:get-changes file-id])
-                    (into (feat.fdata/get-used-pointer-ids data)
-                          (comp (map :data)
-                                (map blob/decode)
-                                (mapcat feat.fdata/get-used-pointer-ids))))
-
-        unused (let [ids (db/create-array conn "uuid" used)]
-                 (->> (db/exec! conn [sql:mark-deleted-data-fragments file-id ids])
-                      (into #{} (map :id))))]
-
-    (doseq [id unused]
-      (l/trc :hint "mark deleted"
-             :rel "file-data-fragment"
-             :id (str id)
-             :file-id (str file-id)))
-
-    (count unused)))
-
-
-(defn- clean-file!
-  [{:keys [::db/conn] :as cfg} {:keys [id data revn modified-at] :as file}]
-
-  (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)
-            pmap/*tracked* (pmap/create-tracked)]
-    (let [data (-> (blob/decode data)
-                   (assoc :id id)
-                   (pmg/migrate-data))
-
-          nfm  (clean-file-media! conn id data)
-          nfot (clean-file-object-thumbnails! cfg id data)
-          nft  (clean-file-thumbnails! cfg id revn)
-          nc   (clean-deleted-components! cfg id data)
-          ndf  (clean-data-fragments! conn id data)]
-
-      (l/dbg :hint "file clened"
-             :file-id (str id)
-             :modified-at (dt/format-instant modified-at)
-             :media-objects nfm
-             :thumbnails nft
-             :object-thumbnails nfot
-             :components nc
-             :data-fragments ndf)
-
-      ;; Mark file as trimmed
-      (db/update! conn :file
-                  {:has-media-trimmed true}
-                  {:id id})
-
-      (feat.fdata/persist-pointers! cfg id))))
diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj
index 510aadd89..a684227c8 100644
--- a/backend/test/backend_tests/rpc_file_test.clj
+++ b/backend/test/backend_tests/rpc_file_test.clj
@@ -154,7 +154,7 @@
 
       ;; Check the number of fragments before adding the page
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
-        (t/is (= 1 (count rows))))
+        (t/is (= 2 (count rows))))
 
       ;; Add page
       (update-file!
@@ -172,15 +172,15 @@
 
       ;; Check the number of fragments
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
-        (t/is (= 2 (count rows))))
+        (t/is (= 5 (count rows))))
 
       ;; The objects-gc should remove unused fragments
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 0 (:processed res))))
+        (t/is (= 1 (:processed res))))
 
       ;; Check the number of fragments
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
-        (t/is (= 2 (count rows))))
+        (t/is (= 4 (count rows))))
 
       ;; Add shape to page that should add a new fragment
       (update-file!
@@ -203,7 +203,7 @@
 
       ;; Check the number of fragments
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
-        (t/is (= 3 (count rows))))
+        (t/is (= 5 (count rows))))
 
       ;; The file-gc should mark for remove unused fragments
       (let [res (th/run-task! :file-gc {:min-age 0})]
@@ -211,12 +211,13 @@
 
       ;; The objects-gc should remove unused fragments
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 0 (:processed res))))
+        (t/is (= 1 (:processed res))))
 
       ;; Check the number of fragments; should be 3 because changes
       ;; are also holding pointers to fragments;
-      (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
-        (t/is (= 3 (count rows))))
+      (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+                                                   :deleted-at nil})]
+        (t/is (= 6 (count rows))))
 
       ;; Lets proceed to delete all changes
       (th/db-delete! :file-change {:file-id (:id file)})
@@ -224,7 +225,6 @@
                      {:has-media-trimmed false}
                      {:id (:id file)})
 
-
       ;; The file-gc should remove fragments related to changes
       ;; snapshots previously deleted.
       (let [res (th/run-task! :file-gc {:min-age 0})]
@@ -233,11 +233,11 @@
       ;; Check the number of fragments;
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
         ;; (pp/pprint rows)
-        (t/is (= 3 (count rows)))
+        (t/is (= 8 (count rows)))
         (t/is (= 2 (count (remove (comp some? :deleted-at) rows)))))
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 1 (:processed res))))
+        (t/is (= 6 (:processed res))))
 
       (let [rows (th/db-query :file-data-fragment {:file-id (:id file)})]
         (t/is (= 2 (count rows)))))))
@@ -367,7 +367,7 @@
         (t/is (= 1 (:processed res))))
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 1 (:processed res))))
+        (t/is (= 2 (:processed res))))
 
       ;; Now that file-gc have deleted the file-media-object usage,
       ;; lets execute the touched-gc task, we should see that two of
@@ -432,6 +432,11 @@
 
           page-id (first (get-in file [:data :pages]))]
 
+
+      (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+                                                   :deleted-at nil})]
+        (t/is (= (count rows) 1)))
+
       ;; Update file inserting a new image object
       (update-file!
        :file-id (:id file)
@@ -491,6 +496,10 @@
       (let [res (th/run-task! :objects-gc {:min-age 0})]
         (t/is (= 1 (:processed res))))
 
+      (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+                                                   :deleted-at nil})]
+        (t/is (= (count rows) 2)))
+
       ;; retrieve file and check trimmed attribute
       (let [row (th/db-get :file {:id (:id file)})]
         (t/is (true? (:has-media-trimmed row))))
@@ -521,11 +530,16 @@
       ;; Now, we have deleted the usage of pointers to the
       ;; file-media-objects, if we paste file-gc, they should be marked
       ;; as deleted.
+
       (let [res (th/run-task! :file-gc {:min-age 0})]
         (t/is (= 1 (:processed res))))
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 5 (:processed res))))
+        (t/is (= 6 (:processed res))))
+
+      (let [rows (th/db-query :file-data-fragment {:file-id (:id file)
+                                                   :deleted-at nil})]
+        (t/is (= (count rows) 3)))
 
       ;; Now that file-gc have deleted the file-media-object usage,
       ;; lets execute the touched-gc task, we should see that two of
@@ -681,7 +695,6 @@
       (let [rows (th/db-query :file-tagged-object-thumbnail {:file-id file-id})]
         (t/is (= 2 (count rows)))
         (t/is (= 1 (count (remove (comp some? :deleted-at) rows))))
-
         (t/is (= (thc/fmt-object-id file-id page-id frame-id-1 "frame")
                  (-> rows first :object-id))))
 
@@ -689,7 +702,7 @@
       ;; thumbnail lets execute the objects-gc task which remove
       ;; the rows and mark as touched the storage object rows
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 2 (:processed res))))
+        (t/is (= 3 (:processed res))))
 
       ;; Now that objects-gc have deleted the object thumbnail lets
       ;; execute the touched-gc task
@@ -719,7 +732,7 @@
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
         ;; (pp/pprint res)
-        (t/is (= 1 (:processed res))))
+        (t/is (= 2 (:processed res))))
 
       ;; We still have th storage objects in the table
       (let [rows (th/db-query :storage-object {:deleted-at nil})]
@@ -736,6 +749,7 @@
         ;; (pp/pprint rows)
         (t/is (= 0 (count rows)))))))
 
+
 (t/deftest permissions-checks-creating-file
   (let [profile1 (th/create-profile* 1)
         profile2 (th/create-profile* 2)
@@ -1147,7 +1161,7 @@
         (t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 2 (:processed res))))
+        (t/is (= 3 (:processed res))))
 
       (let [rows (th/db-query :file-tagged-object-thumbnail {:file-id (:id file)})]
         (t/is (= 1 (count rows)))))))
@@ -1203,7 +1217,7 @@
         (t/is (= 1 (count (remove (comp some? :deleted-at) rows)))))
 
       (let [res (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 1 (:processed res))))
+        (t/is (= 2 (:processed res))))
 
       (let [rows (th/db-query :file-thumbnail {:file-id (:id file)})]
         (t/is (= 1 (count rows)))))))
diff --git a/backend/test/backend_tests/rpc_file_thumbnails_test.clj b/backend/test/backend_tests/rpc_file_thumbnails_test.clj
index 88e2ac2d2..1ad3c6e09 100644
--- a/backend/test/backend_tests/rpc_file_thumbnails_test.clj
+++ b/backend/test/backend_tests/rpc_file_thumbnails_test.clj
@@ -222,7 +222,7 @@
         (t/is (= 1 (:processed result))))
 
       (let [result (th/run-task! :objects-gc {:min-age 0})]
-        (t/is (= 1 (:processed result))))
+        (t/is (= 2 (:processed result))))
 
       ;; check if row1 related thumbnail row still exists
       (let [[row :as rows] (th/db-query :file-thumbnail

From 8ea82021f032cbd4c77d6312f7f5edb1ec6f744a Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Tue, 13 Feb 2024 18:17:43 +0100
Subject: [PATCH 17/23] :sparkles: Add better error report on importing
 truncated binfile

---
 backend/src/app/binfile/v1.clj | 7 +++++++
 backend/src/app/http/sse.clj   | 3 ---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/backend/src/app/binfile/v1.clj b/backend/src/app/binfile/v1.clj
index 207b40ec9..8597254c7 100644
--- a/backend/src/app/binfile/v1.clj
+++ b/backend/src/app/binfile/v1.clj
@@ -40,6 +40,7 @@
    [promesa.util :as pu]
    [yetti.adapter :as yt])
   (:import
+   com.github.luben.zstd.ZstdIOException
    com.github.luben.zstd.ZstdInputStream
    com.github.luben.zstd.ZstdOutputStream
    java.io.DataInputStream
@@ -756,6 +757,12 @@
         (pu/with-open [input (io/input-stream input)]
           (read-import! (assoc cfg ::input input))))
 
+      (catch ZstdIOException cause
+        (ex/raise :type :validation
+                  :code :invalid-penpot-file
+                  :hint "invalid penpot file received: probably truncated"
+                  :cause cause))
+
       (catch Throwable cause
         (vreset! cs cause)
         (throw cause))
diff --git a/backend/src/app/http/sse.clj b/backend/src/app/http/sse.clj
index ec80df72d..868801091 100644
--- a/backend/src/app/http/sse.clj
+++ b/backend/src/app/http/sse.clj
@@ -61,9 +61,6 @@
                            (let [result (handler)]
                              (events/tap :end result))
                            (catch Throwable cause
-                             (binding [l/*context* (errors/request->context request)]
-                               (l/err :hint "unexpected error process streaming response"
-                                      :cause cause))
                              (events/tap :error (errors/handle' cause request)))
                            (finally
                              (sp/close! events/*channel*)

From f152e307370f34005dde6c12834cbcf9eada0fc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Tue, 13 Feb 2024 14:55:08 +0100
Subject: [PATCH 18/23] :bug: Fix shortcuts menu being clipped

---
 .../main/ui/workspace/sidebar/shortcuts.scss  | 351 +++++++++---------
 1 file changed, 179 insertions(+), 172 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
index acd82fd10..530de5577 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.scss
@@ -7,195 +7,202 @@
 @import "refactor/common-refactor.scss";
 
 .shortcuts {
-  .shortcuts-header {
-    @include flexCenter;
-    @include tabTitleTipography;
-    position: relative;
-    height: $s-32;
-    padding: $s-2 $s-2 $s-2 0;
-    margin: $s-4 $s-4 0 $s-4;
-    border-radius: $br-6;
-    background-color: var(--panel-title-background-color);
+  display: grid;
+  grid-template-rows: auto auto 1fr;
+  // TODO: Fix this once we start implementing the DS.
+  //       We should not be doign these hardcoded calc's.
+  height: calc(100vh - #{$s-60});
+}
 
-    .shortcuts-title {
-      @include flexCenter;
-      flex-grow: 1;
-      color: var(--title-foreground-color-hover);
-    }
-
-    .shortcuts-close-button {
-      @extend .button-tertiary;
-      position: absolute;
-      right: $s-2;
-      top: $s-2;
-      height: $s-28;
-      width: $s-28;
-      border-radius: $br-5;
-
-      svg {
-        @extend .button-icon;
-        stroke: var(--icon-foreground);
-      }
-    }
-  }
-
-  .search-field {
-    display: flex;
+.search-field {
+  display: flex;
+  align-items: center;
+  height: $s-32;
+  margin: $s-16 $s-12 $s-4 $s-12;
+  border-radius: $br-8;
+  font-family: "worksans", sans-serif;
+  background-color: var(--color-background-tertiary);
+  .search-box {
     align-items: center;
-    height: $s-32;
-    margin: $s-16 $s-12 $s-4 $s-12;
-    border-radius: $br-8;
-    font-family: "worksans", sans-serif;
-    background-color: var(--color-background-tertiary);
-    .search-box {
-      align-items: center;
+    display: flex;
+    width: 100%;
+
+    .icon-wrapper {
       display: flex;
-      width: 100%;
-
-      .icon-wrapper {
-        display: flex;
-        svg {
-          @extend .button-icon-small;
-          stroke: var(--icon-foreground);
-        }
-      }
-
-      .input-text {
-        @include removeInputStyle;
-        height: $s-32;
-        width: 100%;
-        margin: 0;
-        padding: $s-4;
-        border: 0;
-        font-size: $fs-12;
-        color: var(--color-foreground-primary);
-        &::placeholder {
-          color: var(--color-foreground-secondary);
-        }
-        &:focus-visible {
-          border-color: var(--color-accent-primary-muted);
-        }
-      }
-      .clear-btn {
-        @include buttonStyle;
-        @include flexCenter;
-        height: $s-16;
-        width: $s-16;
-        .clear-icon {
-          @include flexCenter;
-          svg {
-            @extend .button-icon-small;
-            stroke: var(--icon-foreground);
-          }
-        }
-      }
-    }
-    .search-icon {
-      @include flexCenter;
-      width: $s-28;
       svg {
         @extend .button-icon-small;
         stroke: var(--icon-foreground);
       }
     }
-  }
 
-  .section {
-    margin: 0;
-  }
-  .shortcuts-list {
-    display: flex;
-    flex-direction: column;
-    height: 90%;
-    padding: $s-12;
-    margin-bottom: $s-12;
-    overflow-y: overlay;
-    font-size: $fs-12;
-    color: var(--title-foreground-color);
-
-    .section-title,
-    .subsection-title {
-      @include tabTitleTipography;
-      display: flex;
-      align-items: center;
+    .input-text {
+      @include removeInputStyle;
+      height: $s-32;
+      width: 100%;
       margin: 0;
-      padding: $s-8 0;
-      cursor: pointer;
-
-      .collapsed-shortcuts {
+      padding: $s-4;
+      border: 0;
+      font-size: $fs-12;
+      color: var(--color-foreground-primary);
+      &::placeholder {
+        color: var(--color-foreground-secondary);
+      }
+      &:focus-visible {
+        border-color: var(--color-accent-primary-muted);
+      }
+    }
+    .clear-btn {
+      @include buttonStyle;
+      @include flexCenter;
+      height: $s-16;
+      width: $s-16;
+      .clear-icon {
         @include flexCenter;
         svg {
           @extend .button-icon-small;
           stroke: var(--icon-foreground);
         }
-        &.open {
-          transform: rotate(90deg);
-        }
-      }
-      .subsection-name,
-      .section-name {
-        padding-left: $s-4;
-      }
-      &:hover {
-        color: var(--title-foreground-color-hover);
-        .collapsed-shortcuts {
-          svg {
-            stroke: var(--title-foreground-color-hover);
-          }
-        }
-      }
-    }
-
-    .subsection-title {
-      text-transform: none;
-      padding-left: $s-12;
-    }
-    .subsection-menu {
-      margin-bottom: $s-4;
-    }
-    .sub-menu {
-      margin-bottom: $s-4;
-
-      .shortcuts-name {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        width: 100%;
-        min-height: $s-32;
-        padding: $s-6;
-        margin-bottom: $s-4;
-        border-radius: $br-8;
-        background-color: var(--pill-background-color);
-
-        .command-name {
-          @include titleTipography;
-          margin-left: $s-2;
-          color: var(--pill-foreground-color);
-        }
-        .keys {
-          @include flexCenter;
-          gap: $s-2;
-          color: var(--pill-foreground-color);
-
-          .key {
-            @include titleTipography;
-            @include flexCenter;
-            text-transform: capitalize;
-            height: $s-20;
-            padding: $s-2 $s-6;
-            border-radius: $s-6;
-            background-color: var(--menu-shortcut-background-color);
-          }
-          .space {
-            margin: 0 $s-2;
-          }
-        }
       }
     }
   }
-  .not-found {
-    @include titleTipography;
-    color: var(--empty-message-foreground-color);
-    margin: $s-12;
+  .search-icon {
+    @include flexCenter;
+    width: $s-28;
+    svg {
+      @extend .button-icon-small;
+      stroke: var(--icon-foreground);
+    }
+  }
+}
+
+.shortcuts-header {
+  @include flexCenter;
+  @include tabTitleTipography;
+  position: relative;
+  height: $s-32;
+  padding: $s-2 $s-2 $s-2 0;
+  margin: $s-4 $s-4 0 $s-4;
+  border-radius: $br-6;
+  background-color: var(--panel-title-background-color);
+
+  .shortcuts-title {
+    @include flexCenter;
+    flex-grow: 1;
+    color: var(--title-foreground-color-hover);
+  }
+
+  .shortcuts-close-button {
+    @extend .button-tertiary;
+    position: absolute;
+    right: $s-2;
+    top: $s-2;
+    height: $s-28;
+    width: $s-28;
+    border-radius: $br-5;
+
+    svg {
+      @extend .button-icon;
+      stroke: var(--icon-foreground);
+    }
+  }
+}
+
+.section {
+  margin: 0;
+}
+
+.not-found {
+  @include titleTipography;
+  color: var(--empty-message-foreground-color);
+  margin: $s-12;
+}
+
+.shortcuts-list {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  padding: $s-12;
+  overflow-y: scroll;
+  font-size: $fs-12;
+  color: var(--title-foreground-color);
+
+  .section-title,
+  .subsection-title {
+    @include tabTitleTipography;
+    display: flex;
+    align-items: center;
+    margin: 0;
+    padding: $s-8 0;
+    cursor: pointer;
+
+    .collapsed-shortcuts {
+      @include flexCenter;
+      svg {
+        @extend .button-icon-small;
+        stroke: var(--icon-foreground);
+      }
+      &.open {
+        transform: rotate(90deg);
+      }
+    }
+    .subsection-name,
+    .section-name {
+      padding-left: $s-4;
+    }
+    &:hover {
+      color: var(--title-foreground-color-hover);
+      .collapsed-shortcuts {
+        svg {
+          stroke: var(--title-foreground-color-hover);
+        }
+      }
+    }
+  }
+
+  .subsection-title {
+    text-transform: none;
+    padding-left: $s-12;
+  }
+  .subsection-menu {
+    margin-bottom: $s-4;
+  }
+  .sub-menu {
+    margin-bottom: $s-4;
+
+    .shortcuts-name {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+      min-height: $s-32;
+      padding: $s-6;
+      margin-bottom: $s-4;
+      border-radius: $br-8;
+      background-color: var(--pill-background-color);
+
+      .command-name {
+        @include titleTipography;
+        margin-left: $s-2;
+        color: var(--pill-foreground-color);
+      }
+      .keys {
+        @include flexCenter;
+        gap: $s-2;
+        color: var(--pill-foreground-color);
+
+        .key {
+          @include titleTipography;
+          @include flexCenter;
+          text-transform: capitalize;
+          height: $s-20;
+          padding: $s-2 $s-6;
+          border-radius: $s-6;
+          background-color: var(--menu-shortcut-background-color);
+        }
+        .space {
+          margin: 0 $s-2;
+        }
+      }
+    }
   }
 }

From d654a4faedc2917fb3162f2e469e3c8792167014 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Tue, 13 Feb 2024 14:43:30 +0100
Subject: [PATCH 19/23] :bug: Avoid setting touched in parent when swapping
 components

---
 .../src/app/common/files/changes_builder.cljc | 13 ++++++++-----
 .../app/main/data/workspace/libraries.cljs    |  3 ++-
 .../src/app/main/data/workspace/shapes.cljs   | 19 +++++++++----------
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc
index f5d1cf201..f868cbd2a 100644
--- a/common/src/app/common/files/changes_builder.cljc
+++ b/common/src/app/common/files/changes_builder.cljc
@@ -326,7 +326,9 @@
            (some? index)
            (assoc :index index)
            (:component-swap options)
-           (assoc :component-swap true))
+           (assoc :component-swap true)
+           (:ignore-touched options)
+           (assoc :ignore-touched true))
 
          mk-undo-change
          (fn [undo-changes shape]
@@ -450,10 +452,11 @@
          add-redo-change
          (fn [change-set id]
            (conj change-set
-                 {:type :del-obj
-                  :page-id page-id
-                  :ignore-touched ignore-touched
-                  :id id}))
+                 (cond-> {:type :del-obj
+                          :page-id page-id
+                          :id id}
+                   ignore-touched
+                   (assoc :ignore-touched true))))
 
          add-undo-change-shape
          (fn [change-set id]
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index 1fab0ddcd..5edcafbe0 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -898,7 +898,8 @@
                 (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
 
                 ;; We need to set the same index as the original shape
-                (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true}))]
+                (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true
+                                                                         :ignore-touched true}))]
 
         ;; First delete so we don't break the grid layout cells
         (rx/of (dch/commit-changes changes)
diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs
index 99e13369a..805669e4d 100644
--- a/frontend/src/app/main/data/workspace/shapes.cljs
+++ b/frontend/src/app/main/data/workspace/shapes.cljs
@@ -142,17 +142,17 @@
          (rx/concat
           (rx/of (dwu/start-undo-transaction undo-id)
                  (update-shape-flags ids-to-hide {:hidden true}))
-          (real-delete-shapes file page objects ids-to-delete it components-v2)
+          (real-delete-shapes file page objects ids-to-delete it components-v2 (:component-swap options))
           (rx/of (dwu/commit-undo-transaction undo-id))))))))
 
 (defn- real-delete-shapes-changes
-  ([file page objects ids it components-v2]
+  ([file page objects ids it components-v2 ignore-touched]
    (let [changes (-> (pcb/empty-changes it (:id page))
                      (pcb/with-page page)
                      (pcb/with-objects objects)
                      (pcb/with-library-data file))]
-     (real-delete-shapes-changes changes file page objects ids it components-v2)))
-  ([changes file page objects ids _it components-v2]
+     (real-delete-shapes-changes changes file page objects ids it components-v2 ignore-touched)))
+  ([changes file page objects ids _it components-v2 ignore-touched]
    (let [lookup  (d/getf objects)
          groups-to-unmask
          (reduce (fn [group-ids id]
@@ -252,7 +252,7 @@
 
          changes (-> changes
                      (pcb/remove-objects all-children {:ignore-touched true})
-                     (pcb/remove-objects ids)
+                     (pcb/remove-objects ids {:ignore-touched ignore-touched})
                      (pcb/remove-objects empty-parents)
                      (pcb/resize-parents all-parents)
                      (pcb/update-shapes groups-to-unmask
@@ -274,13 +274,13 @@
 
 
 (defn delete-shapes-changes
-  [changes file page objects ids it components-v2]
-  (let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2)]
+  [changes file page objects ids it components-v2 ignore-touched]
+  (let [[changes _all-parents] (real-delete-shapes-changes changes file page objects ids it components-v2 ignore-touched)]
     changes))
 
 (defn- real-delete-shapes
-  [file page objects ids it components-v2]
-  (let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2)
+  [file page objects ids it components-v2 ignore-touched]
+  (let [[changes all-parents] (real-delete-shapes-changes file page objects ids it components-v2 ignore-touched)
         undo-id (js/Symbol)]
     (rx/of (dwu/start-undo-transaction undo-id)
            (dc/detach-comment-thread ids)
@@ -288,7 +288,6 @@
            (ptk/data-event :layout/update all-parents)
            (dwu/commit-undo-transaction undo-id))))
 
-
 (defn create-and-add-shape
   [type frame-x frame-y {:keys [width height] :as attrs}]
   (ptk/reify ::create-and-add-shape

From fa19ce2b5b90298fa639718949a9c498ce08326f Mon Sep 17 00:00:00 2001
From: Alejandro Alonso <alejandroalonsofernandez@gmail.com>
Date: Wed, 14 Feb 2024 07:58:21 +0100
Subject: [PATCH 20/23] :bug: Fix change stroke color from library doesn't work

---
 frontend/src/app/main/data/workspace/colors.cljs    | 4 ++--
 frontend/src/app/main/ui/workspace/colorpicker.cljs | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs
index 1d4afbd9b..7c31ef43b 100644
--- a/frontend/src/app/main/data/workspace/colors.cljs
+++ b/frontend/src/app/main/data/workspace/colors.cljs
@@ -442,6 +442,7 @@
 (declare activate-colorpicker-color)
 (declare activate-colorpicker-gradient)
 (declare activate-colorpicker-image)
+(declare update-colorpicker)
 
 (defn apply-color-from-colorpicker
   [color]
@@ -453,8 +454,7 @@
          (:image color) (activate-colorpicker-image)
          (:color color) (activate-colorpicker-color)
          (= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient)
-         (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))
-       (apply-color-from-palette color false)))))
+         (= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))))))
 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index c98caaa6c..e74f83525 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -145,7 +145,8 @@
         on-select-library-color
         (mf/use-fn
          (fn [_ color]
-           (st/emit! (dc/apply-color-from-colorpicker color))))
+           (st/emit! (dc/apply-color-from-colorpicker color))
+           (on-change color)))
 
         on-add-library-color
         (mf/use-fn

From 8ead63cad0ad653009938a31de37eb29ef572a14 Mon Sep 17 00:00:00 2001
From: Eva <eva.marco@kaleidos.net>
Date: Wed, 7 Feb 2024 19:43:52 +0100
Subject: [PATCH 21/23] :recycle: Review inspect tab spacing

---
 .../styles/common/refactor/mixins.scss        |   4 +-
 .../app/main/ui/components/color_bullet.cljs  |   2 +-
 .../main/ui/components/color_bullet_new.cljs  |  18 +-
 .../main/ui/components/color_bullet_new.scss  |   4 +
 .../app/main/ui/components/copy_button.scss   |  30 ++--
 .../src/app/main/ui/components/select.cljs    |  10 +-
 .../src/app/main/ui/components/title_bar.cljs |   7 +-
 .../src/app/main/ui/components/title_bar.scss | 107 ++++++-----
 .../main/ui/viewer/inspect/attributes.scss    |   1 +
 .../ui/viewer/inspect/attributes/blur.cljs    |   8 +-
 .../ui/viewer/inspect/attributes/blur.scss    |   4 +
 .../ui/viewer/inspect/attributes/common.cljs  | 167 ++++++++++--------
 .../ui/viewer/inspect/attributes/common.scss  |  87 ++++++---
 .../ui/viewer/inspect/attributes/fill.cljs    |   2 +-
 .../ui/viewer/inspect/attributes/fill.scss    |   5 +
 .../viewer/inspect/attributes/geometry.cljs   |  19 +-
 .../viewer/inspect/attributes/geometry.scss   |   4 +
 .../ui/viewer/inspect/attributes/image.cljs   |   2 +-
 .../ui/viewer/inspect/attributes/layout.cljs  |  21 ++-
 .../ui/viewer/inspect/attributes/layout.scss  |   4 +
 .../inspect/attributes/layout_element.cljs    |  17 +-
 .../inspect/attributes/layout_element.scss    |   4 +
 .../ui/viewer/inspect/attributes/shadow.cljs  |   2 +
 .../ui/viewer/inspect/attributes/stroke.cljs  |   1 +
 .../ui/viewer/inspect/attributes/stroke.scss  |   5 +
 .../ui/viewer/inspect/attributes/svg.cljs     |  21 ++-
 .../ui/viewer/inspect/attributes/text.cljs    |   7 +-
 .../ui/viewer/inspect/attributes/text.scss    |   2 +-
 .../src/app/main/ui/viewer/inspect/code.cljs  |  38 ++--
 .../src/app/main/ui/viewer/inspect/code.scss  |  23 ++-
 .../main/ui/viewer/inspect/right_sidebar.scss |  10 +-
 .../app/main/ui/workspace/color_palette.cljs  |   2 +-
 .../src/app/main/ui/workspace/sidebar.scss    |   7 +-
 .../app/main/ui/workspace/sidebar/layers.scss |   3 +-
 frontend/translations/en.po                   |   3 +
 frontend/translations/es.po                   |   3 +
 36 files changed, 403 insertions(+), 251 deletions(-)

diff --git a/frontend/resources/styles/common/refactor/mixins.scss b/frontend/resources/styles/common/refactor/mixins.scss
index f988cd0df..2b9a05efd 100644
--- a/frontend/resources/styles/common/refactor/mixins.scss
+++ b/frontend/resources/styles/common/refactor/mixins.scss
@@ -115,8 +115,8 @@
 @mixin copyWrapperBase {
   position: relative;
   min-height: $s-32;
-  width: $s-156;
-  max-width: $s-156;
+  width: $s-144;
+  max-width: $s-144;
   padding: calc($s-8 - $s-1) 0 calc($s-8 - $s-1) calc($s-8 - $s-1);
   border-radius: $s-8;
   box-sizing: border-box;
diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs
index 0b273aac2..7213a8347 100644
--- a/frontend/src/app/main/ui/components/color_bullet.cljs
+++ b/frontend/src/app/main/ui/components/color_bullet.cljs
@@ -57,5 +57,5 @@
         :on-double-click on-double-click
         :title name}
        (if (some? image)
-         (tr "media.image")
+         (tr "media.image.short")
          (or name color (uc/gradient-type->string (:type gradient))))])))
diff --git a/frontend/src/app/main/ui/components/color_bullet_new.cljs b/frontend/src/app/main/ui/components/color_bullet_new.cljs
index ff333f35e..6173a582f 100644
--- a/frontend/src/app/main/ui/components/color_bullet_new.cljs
+++ b/frontend/src/app/main/ui/components/color_bullet_new.cljs
@@ -9,7 +9,7 @@
   (:require
    [app.config :as cfg]
    [app.util.color :as uc]
-   [app.util.i18n :as i18n :refer [tr]]
+   [app.util.i18n :refer [tr]]
    [cuerdas.core :as str]
    [rumext.v2 :as mf]))
 
@@ -99,16 +99,16 @@
 
 (mf/defc color-name
   {::mf/wrap-props false}
-  [{:keys [color size on-click on-double-click]}]
-  (let [{:keys [name color gradient image]} (if (string? color) {:color color :opacity 1} color)]
+  [{:keys [color size on-click on-double-click origin]}]
+  (let [{:keys [name color gradient]} (if (string? color) {:color color :opacity 1} color)]
     (when (or (not size) (> size 64))
       [:span {:class (stl/css-case
-                      :color-text (< size 72)
-                      :small-text (and (>= size 64) (< size 72))
-                      :big-text   (>= size 72))
+                      :color-text (and (= origin :palette) (< size 72))
+                      :small-text (and (= origin :palette) (>= size 64) (< size 72))
+                      :big-text   (and (= origin :palette) (>= size 72))
+                      :gradient   (some? gradient)
+                      :color-row-name (not=  origin :palette))
               :title name
               :on-click on-click
               :on-double-click on-double-click}
-       (if (some? image)
-         (or name (tr "media.image"))
-         (or name color (uc/gradient-type->string (:type gradient))))])))
+       (or name color (uc/gradient-type->string (:type gradient)))])))
diff --git a/frontend/src/app/main/ui/components/color_bullet_new.scss b/frontend/src/app/main/ui/components/color_bullet_new.scss
index d6e617251..2dddb14b9 100644
--- a/frontend/src/app/main/ui/components/color_bullet_new.scss
+++ b/frontend/src/app/main/ui/components/color_bullet_new.scss
@@ -94,3 +94,7 @@
 .no-text {
   display: none;
 }
+
+.color-row-name {
+  color: var(--menu-foreground-color);
+}
diff --git a/frontend/src/app/main/ui/components/copy_button.scss b/frontend/src/app/main/ui/components/copy_button.scss
index c81487f45..ec8e362bb 100644
--- a/frontend/src/app/main/ui/components/copy_button.scss
+++ b/frontend/src/app/main/ui/components/copy_button.scss
@@ -8,9 +8,8 @@
 
 .copy-button {
   @include buttonStyle;
-  @include flexCenter;
+  width: 100%;
   height: $s-32;
-  width: $s-32;
   border: $s-1 solid transparent;
   border-radius: $br-8;
   background-color: transparent;
@@ -51,41 +50,32 @@
 .copy-wrapper {
   @include buttonStyle;
   @include copyWrapperBase;
-  display: grid;
-  grid-template-columns: 1fr $s-24;
-  grid-template-areas: "name button";
   width: 100%;
   height: fit-content;
   text-align: left;
-  border: 1px solid transparent;
+  border: $s-1 solid transparent;
   .icon-btn {
+    @include flexCenter;
     position: absolute;
-    display: flex;
-    justify-content: center;
-    align-items: center;
     top: 0;
     right: 0;
     height: $s-32;
-    width: $s-32;
+    width: $s-28;
     svg {
       @extend .button-icon-small;
+      stroke: var(--button-tertiary-foreground-color-focus);
       display: none;
     }
   }
   &:hover {
-    .icon-btn {
-      svg {
-        display: flex;
-        stroke: var(--button-tertiary-foreground-color-active);
-      }
+    background-color: var(--button-tertiary-background-color-focus);
+    color: var(--button-tertiary-foreground-color-focus);
+    border: $s-1 solid var(--button-tertiary-background-color-focus);
+    .icon-btn svg {
+      display: flex;
     }
   }
 
-  &:hover {
-    background-color: var(--color-background-tertiary);
-    color: var(--color-foreground-primary);
-    border: $s-1 solid var(--color-background-tertiary);
-  }
   &:focus,
   &:focus-visible {
     outline: none;
diff --git a/frontend/src/app/main/ui/components/select.cljs b/frontend/src/app/main/ui/components/select.cljs
index e3ae25732..ffacb7055 100644
--- a/frontend/src/app/main/ui/components/select.cljs
+++ b/frontend/src/app/main/ui/components/select.cljs
@@ -97,15 +97,17 @@
           current-icon (:icon selected-option)
           current-icon-ref (i/key->icon current-icon)]
       [:div {:on-click open-dropdown
-             :class (dm/str class " " (stl/css-case :custom-select true
-                                                    :disabled disabled
-                                                    :icon (some? current-icon-ref)))}
+             :class (dm/str (stl/css-case :custom-select true
+                                          :disabled disabled
+                                          :icon (some? current-icon-ref))
+                            " " class)}
        (when (and current-icon current-icon-ref)
          [:span {:class (stl/css :current-icon)} current-icon-ref])
        [:span {:class (stl/css :current-label)} current-label]
        [:span {:class (stl/css :dropdown-button)} i/arrow-refactor]
        [:& dropdown {:show is-open? :on-close close-dropdown}
-        [:ul {:ref dropdown-element* :data-direction @dropdown-direction* :class (dm/str dropdown-class " " (stl/css :custom-select-dropdown))}
+        [:ul {:ref dropdown-element* :data-direction @dropdown-direction*
+              :class (dm/str (stl/css :custom-select-dropdown) " " dropdown-class)}
          (for [[index item] (d/enumerate options)]
            (if (= :separator item)
              [:li {:class (dom/classnames (stl/css :separator) true)
diff --git a/frontend/src/app/main/ui/components/title_bar.cljs b/frontend/src/app/main/ui/components/title_bar.cljs
index 0c9899bbc..dcce61678 100644
--- a/frontend/src/app/main/ui/components/title_bar.cljs
+++ b/frontend/src/app/main/ui/components/title_bar.cljs
@@ -13,7 +13,7 @@
 
 (mf/defc title-bar
   {::mf/wrap-props false}
-  [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable add-icon-gap]}]
+  [{:keys [collapsable collapsed on-collapsed title children on-btn-click btn-children class all-clickable add-icon-gap origin]}]
   (let [klass (dm/str (stl/css :title-bar) " " class)]
     [:div {:class klass}
      (if ^boolean collapsable
@@ -33,7 +33,10 @@
                      :on-click on-collapsed}
             i/arrow-refactor]
            [:div {:class (stl/css :title)} title]])]
-       [:div {:class (stl/css-case :title-only true :title-only-icon-gap add-icon-gap)} title])
+       [:div {:class (stl/css-case :title-only true
+                                   :title-only-icon-gap add-icon-gap
+                                   :title-only (not= :inspect origin)
+                                   :inspect-title (= :inspect origin))} title])
      children
      (when (some? on-btn-click)
        [:button {:class (stl/css :title-button)
diff --git a/frontend/src/app/main/ui/components/title_bar.scss b/frontend/src/app/main/ui/components/title_bar.scss
index e76b9fe2b..bfbd756fc 100644
--- a/frontend/src/app/main/ui/components/title_bar.scss
+++ b/frontend/src/app/main/ui/components/title_bar.scss
@@ -14,8 +14,39 @@
   width: 100%;
   min-height: $s-32;
   background-color: var(--title-background-color);
+}
 
-  .title-wrapper {
+.title,
+.title-only,
+.inspect-title {
+  @include tabTitleTipography;
+  display: flex;
+  align-items: center;
+  flex-grow: 1;
+  height: 100%;
+  min-height: $s-32;
+  color: var(--title-foreground-color);
+  overflow: hidden;
+}
+
+.title-only {
+  margin-left: $s-8;
+}
+
+.inspect-title {
+  color: var(--title-foreground-color-hover);
+}
+
+.title-wrapper {
+  display: flex;
+  align-items: center;
+  flex-grow: 1;
+  padding: 0;
+  color: var(--title-foreground-color);
+  stroke: var(--title-foreground-color);
+  overflow: hidden;
+  .toggle-btn {
+    @include buttonStyle;
     display: flex;
     align-items: center;
     flex-grow: 1;
@@ -23,42 +54,7 @@
     color: var(--title-foreground-color);
     stroke: var(--title-foreground-color);
     overflow: hidden;
-    .toggle-btn {
-      @include buttonStyle;
-      display: flex;
-      align-items: center;
-      flex-grow: 1;
-      padding: 0;
-      color: var(--title-foreground-color);
-      stroke: var(--title-foreground-color);
-      overflow: hidden;
-      .collapsabled-icon {
-        @include flexCenter;
-        height: $s-24;
-        border-radius: $br-8;
-        svg {
-          @extend .button-icon-small;
-          transform: rotate(90deg);
-          stroke: var(--icon-foreground);
-        }
-        &.rotated svg {
-          transform: rotate(0deg);
-        }
-      }
-      &:hover {
-        color: var(--title-foreground-color-hover);
-        stroke: var(--title-foreground-color-hover);
-        .title {
-          color: var(--title-foreground-color-hover);
-          stroke: var(--title-foreground-color-hover);
-        }
-        .collapsabled-icon svg {
-          stroke: var(--title-foreground-color-hover);
-        }
-      }
-    }
     .collapsabled-icon {
-      @include buttonStyle;
       @include flexCenter;
       height: $s-24;
       border-radius: $br-8;
@@ -75,6 +71,7 @@
       color: var(--title-foreground-color-hover);
       stroke: var(--title-foreground-color-hover);
       .title {
+        color: var(--title-foreground-color-hover);
         stroke: var(--title-foreground-color-hover);
       }
       .collapsabled-icon svg {
@@ -82,17 +79,41 @@
       }
     }
   }
-
-  .title-button {
-    @extend .button-tertiary;
-    height: $s-32;
-    width: calc($s-24 + $s-4);
-    padding: 0;
+  .collapsabled-icon {
+    @include buttonStyle;
+    @include flexCenter;
+    height: $s-24;
     border-radius: $br-8;
     svg {
-      @extend .button-icon;
+      @extend .button-icon-small;
+      transform: rotate(90deg);
       stroke: var(--icon-foreground);
     }
+    &.rotated svg {
+      transform: rotate(0deg);
+    }
+  }
+  &:hover {
+    color: var(--title-foreground-color-hover);
+    stroke: var(--title-foreground-color-hover);
+    .title {
+      stroke: var(--title-foreground-color-hover);
+    }
+    .collapsabled-icon svg {
+      stroke: var(--title-foreground-color-hover);
+    }
+  }
+}
+
+.title-button {
+  @extend .button-tertiary;
+  height: $s-32;
+  width: calc($s-24 + $s-4);
+  padding: 0;
+  border-radius: $br-8;
+  svg {
+    @extend .button-icon;
+    stroke: var(--icon-foreground);
   }
 }
 
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.scss b/frontend/src/app/main/ui/viewer/inspect/attributes.scss
index 17a761d01..54980db83 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes.scss
@@ -12,4 +12,5 @@
   gap: $s-16;
   width: 100%;
   height: 100%;
+  padding-top: $s-8;
 }
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
index 162fb5db8..3b006e94c 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.cljs
@@ -7,6 +7,7 @@
 (ns app.main.ui.viewer.inspect.attributes.blur
   (:require-macros [app.main.style :as stl])
   (:require
+   [app.common.data.macros :as dm]
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.main.ui.components.title-bar :refer [title-bar]]
    [app.util.code-gen.style-css :as css]
@@ -23,13 +24,16 @@
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
                       :title       (tr "inspect.attributes.blur")
+                      :origin      :inspect
                       :class       (stl/css :title-spacing-blur)}
         (when (= (count shapes) 1)
-          [:& copy-button {:data (css/get-css-property objects (first shapes) :filter)}])]
+          [:& copy-button {:data  (css/get-css-property objects (first shapes) :filter)
+                           :class (stl/css :copy-btn-title)}])]
 
        [:div {:class (stl/css :attributes-content)}
         (for [shape shapes]
-          [:div {:class (stl/css :blur-row)}
+          [:div {:class (stl/css :blur-row)
+                 :key (dm/str "block-" (:id shape) "-blur")}
            [:div {:class (stl/css :global/attr-label)} "Filter"]
            [:div {:class (stl/css :global/attr-value)}
             [:& copy-button {:data (css/get-css-property objects shape :filter)}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
index e2da40708..a3f2dc334 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/blur.scss
@@ -21,3 +21,7 @@
 .button-children {
   @extend .copy-button-children;
 }
+
+.copy-btn-title {
+  max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
index 76001fe46..bbf5148f3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.cljs
@@ -8,6 +8,8 @@
   (:require-macros [app.main.style :as stl])
   (:require
    [app.common.colors :as cc]
+   [app.common.data :as d]
+   [app.common.data.macros :as dm]
    [app.common.media :as cm]
    [app.config :as cf]
    [app.main.refs :as refs]
@@ -15,6 +17,7 @@
    [app.main.ui.components.color-bullet-new :as cbn]
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.main.ui.components.select :refer [select]]
+   [app.main.ui.formats :as fmt]
    [app.util.i18n :refer [tr]]
    [cuerdas.core :as str]
    [okulary.core :as l]
@@ -43,6 +46,13 @@
 (defn- get-file-colors []
   (or (mf/deref file-colors-ref) (mf/deref refs/workspace-file-colors)))
 
+(defn get-css-rule-humanized [property]
+  (as-> property $
+    (d/name $)
+    (str/split $ "-")
+    (str/join " " $)
+    (str/capital $)))
+
 (mf/defc color-row [{:keys [color format copy-data on-change-format]}]
   (let [colors-library     (get-colors-library color)
         file-colors        (get-file-colors)
@@ -50,85 +60,90 @@
         color              (assoc color :color-library-name color-library-name)
         image              (:image color)]
 
-    [:*
-     [:div {:class (stl/css :attributes-color-row)}
-      [:div {:class (stl/css :bullet-wrapper)
-             :style #js {"--bullet-size" "16px"}}
-       [:& cbn/color-bullet {:color color
-                             :mini? true}]]
 
-      (when-not image
-        [:div {:class (stl/css :format-wrapper)}
-         (when-not (and on-change-format (or (:gradient color) image))
-           [:div {:class (stl/css :select-format-wrapper)}
-            [:& select
-             {:default-value format
-              :options [{:value :hex :label (tr "inspect.attributes.color.hex")}
-                        {:value :rgba :label (tr "inspect.attributes.color.rgba")}
-                        {:value :hsla :label (tr "inspect.attributes.color.hsla")}]
-              :on-change on-change-format}]])
-         (when (:gradient color)
-           [:div {:class (stl/css :format-info)} "rgba"])])
+    (if image
+      (let [mtype     (-> image :mtype)
+            name      (or (:name image) (tr "media.image"))
+            extension (cm/mtype->extension mtype)]
+        [:div {:class (stl/css :attributes-image-as-color-row)}
+         [:div {:class (stl/css :attributes-color-row)}
+          [:div {:class (stl/css :bullet-wrapper)
+                 :style #js {"--bullet-size" "16px"}}
+           [:& cbn/color-bullet {:color color
+                                 :mini? true}]]
 
-      (if (and copy-data (not image))
-        [:& copy-button {:data copy-data
-                         :class (stl/css :color-row-copy-btn)}
-         [:*
-          [:div {:class (stl/css :first-row)}
-           [:div {:class (stl/css :name-opacity)}
-            [:span {:class (stl/css-case :color-value-wrapper true
-                                         :gradient-name (:gradient color))}
-             (if (:gradient color)
-               [:& cbn/color-name {:color color :size 80}]
-               (case format
-                 :hex [:& cbn/color-name {:color color}]
-                 :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
-                         [:* (str/fmt "%s, %s, %s, %s" r g b a)])
-                 :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
-                             result (cc/format-hsla [h s l a])]
-                         [:* result])))]
-
-            (when-not (:gradient color)
-              [:span {:class (stl/css :opacity-info)}
-               (str (* 100 (:opacity color)) "%")])]]
-
-          (when color-library-name
-            [:div {:class (stl/css :second-row)}
-             [:div {:class (stl/css :color-name-library)}
-              color-library-name]])]]
-
-        [:div {:class (stl/css :color-info)}
-         [:div {:class (stl/css :first-row)}
-          [:div {:class (stl/css :name-opacity)}
-           [:span {:class (stl/css-case :color-value-wrapper true
-                                        :gradient-name (:gradient color))}
-            (if (:gradient color)
-              [:& cbn/color-name {:color color}]
-              (case format
-                :hex [:& cbn/color-name {:color color
-                                         :size 80}]
-                :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
-                        [:* (str/fmt "%s, %s, %s, %s" r g b a)])
-                :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
-                            result (cc/format-hsla [h s l a])]
-                        [:* result])))]
-
-           (when-not (:gradient color)
+          [:div {:class (stl/css :format-wrapper)}
+           [:div {:class (stl/css :image-format)}
+            (tr "media.image.short")]]
+          [:& copy-button {:data copy-data
+                           :class (stl/css :color-row-copy-btn)}
+           [:div {:class (stl/css-case :color-info true
+                                       :two-line (some? color-library-name))}
+            [:div {:class (stl/css :first-row)}
              [:span {:class (stl/css :opacity-info)}
-              (str (* 100 (:opacity color)) "%")])]]
+              (str (* 100 (:opacity color)) "%")]]
 
-         (when color-library-name
-           [:div {:class (stl/css :second-row)}
-            [:div {:class (stl/css :color-name-library)}
-             color-library-name]])])]
+            (when color-library-name
+              [:div {:class (stl/css :second-row)}
+               [:div {:class (stl/css :color-name-library)}
+                color-library-name]])]]
 
-     (when image
-       (let [mtype     (-> image :mtype)
-             name      (or (:name image) (tr "media.image"))
-             extension (cm/mtype->extension mtype)]
-         [:a {:class (stl/css :download-button)
-              :target "_blank"
-              :download (cond-> name extension (str/concat extension))
-              :href (cf/resolve-file-media image)}
-          (tr "inspect.attributes.image.download")]))]))
+          [:div {:class (stl/css :image-download)}
+           [:div {:class (stl/css :image-wrapper)}
+            [:img {:src (cf/resolve-file-media image)}]]
+
+           [:a {:class (stl/css :download-button)
+                :target "_blank"
+                :download (cond-> name extension (str/concat extension))
+                :href (cf/resolve-file-media image)}
+            (tr "inspect.attributes.image.download")]]]])
+
+      [:div {:class (stl/css :attributes-color-row)}
+       [:div {:class (stl/css :bullet-wrapper)
+              :style #js {"--bullet-size" "16px"}}
+        [:& cbn/color-bullet {:color color
+                              :mini? true}]]
+
+       [:div {:class (stl/css :format-wrapper)}
+        (when-not (and on-change-format (or (:gradient color) image))
+          [:& select
+           {:default-value format
+            :class (stl/css :select-format-wrapper)
+            :options [{:value :hex :label (tr "inspect.attributes.color.hex")}
+                      {:value :rgba :label (tr "inspect.attributes.color.rgba")}
+                      {:value :hsla :label (tr "inspect.attributes.color.hsla")}]
+            :on-change on-change-format}])
+        (when (:gradient color)
+          [:div {:class (stl/css :format-info)} "rgba"])]
+
+       [:& copy-button {:data copy-data
+                        :class (stl/css-case :color-row-copy-btn true
+                                             :one-line (not color-library-name)
+                                             :two-line (some? color-library-name))}
+        [:div {:class (stl/css :first-row)}
+         [:div {:class (stl/css :name-opacity)}
+          [:span {:class (stl/css-case :color-value-wrapper true
+                                       :gradient-name (:gradient color))}
+           (if (:gradient color)
+             [:& cbn/color-name {:color color :size 90}]
+             (case format
+               :hex [:& cbn/color-name {:color color}]
+               :rgba (let [[r g b a] (cc/hex->rgba (:color color) (:opacity color))]
+                       (str/ffmt "%, %, %, %" r g b a))
+               :hsla (let [[h s l a] (cc/hex->hsla (:color color) (:opacity color))
+                           result (cc/format-hsla [h s l a])]
+                       [:* result])))]
+
+          (when-not (:gradient color)
+            [:span {:class (stl/css :opacity-info)}
+             (dm/str (-> color
+                         (:opacity)
+                         (d/coalesce 1)
+                         (* 100)
+                         (fmt/format-number)) "%")])]]
+
+        (when color-library-name
+          [:div {:class (stl/css :second-row)}
+           [:div {:class (stl/css :color-name-library)}
+            color-library-name]])]])))
 
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
index f05e0faa4..7f30931c3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/common.scss
@@ -6,9 +6,13 @@
 
 @import "refactor/common-refactor.scss";
 
+.attributes-image-as-color-row {
+  max-width: $s-240;
+}
+
 .attributes-color-row {
   display: grid;
-  grid-template-columns: $s-16 $s-72 $s-156;
+  grid-template-columns: $s-16 $s-72 $s-144;
   gap: $s-4;
 }
 
@@ -22,14 +26,19 @@
   height: $s-32;
 }
 
+.image-format {
+  @include tabTitleTipography;
+  height: $s-32;
+  padding: $s-8 0;
+  color: var(--menu-foreground-color-rest);
+}
+
 .select-format-wrapper {
   width: 100%;
-  div {
-    background-color: transparent;
-    border: none;
-    padding-left: $s-2;
-    color: var(--menu-foreground-color-rest);
-  }
+  padding: $s-8 $s-2;
+  background-color: transparent;
+  border-color: transparent;
+  color: var(--menu-foreground-color-rest);
 }
 
 .format-info {
@@ -43,12 +52,16 @@
   color: var(--menu-foreground-color-rest);
 }
 
+.color-row-copy-btn {
+  max-width: $s-144;
+}
+
 .color-info {
   display: flex;
   align-items: flex-start;
   gap: $s-4;
   flex-grow: 1;
-
+  max-width: $s-144;
   button {
     visibility: hidden;
     min-width: $s-28;
@@ -57,19 +70,22 @@
     visibility: visible;
   }
 }
-
-.name-opacity {
-  display: flex;
-  align-items: baseline;
+.one-line {
+  max-height: $s-32;
+}
+.two-line {
+  display: grid;
+  grid-template-rows: 1fr 1fr;
 }
-
 .color-name-wrapper {
   @include titleTipography;
   @include flexColumn;
   padding: $s-8 $s-4 $s-8 $s-8;
   height: $s-32;
   max-width: $s-80;
+
   &.gradient-color {
+    color: var(--menu-foreground-color);
     max-width: $s-124;
   }
   .color-name-library {
@@ -92,16 +108,9 @@
   padding: $s-8 0;
 }
 
-.color-info,
-.color-row-copy-btn {
-  display: flex;
-  max-width: $s-144;
-}
-
 .first-row {
   display: grid;
-  grid-template-columns: 1fr $s-24;
-  grid-template-areas: "name button";
+  grid-template-columns: 1fr $s-28;
   height: fit-content;
   width: 100%;
   padding: 0;
@@ -109,27 +118,26 @@
 }
 
 .name-opacity {
-  grid-area: name;
   height: fit-content;
-  max-width: $s-124;
+  width: 100%;
   line-height: $s-16;
+  display: grid;
+  grid-template-columns: 1fr auto;
 }
 
 .color-value-wrapper {
+  @include textEllipsis;
   @include inspectValue;
   text-transform: uppercase;
-  max-width: $s-80;
-  padding-right: $s-8;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
   &.gradient-name {
     text-transform: none;
   }
 }
+
 .opacity-info {
   @include inspectValue;
   text-transform: uppercase;
+  width: 100%;
 }
 
 .second-row {
@@ -146,9 +154,32 @@
   color: var(--menu-foreground-color-rest);
 }
 
+.image-download {
+  grid-column: 1 / 4;
+}
+
 .download-button {
   @extend .button-secondary;
   @include tabTitleTipography;
   height: $s-32;
+  width: 100%;
   margin-top: $s-4;
 }
+
+.image-wrapper {
+  background-color: var(--menu-background-color);
+  position: relative;
+  @include flexCenter;
+  width: $s-240;
+  height: $s-160;
+  max-height: $s-160;
+  max-width: $s-248;
+  margin: $s-8 0;
+  border-radius: $br-8;
+
+  img {
+    height: 100%;
+    width: 100%;
+    object-fit: contain;
+  }
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
index 8383ca085..85f607333 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs
@@ -35,7 +35,6 @@
   [{:keys [objects shape]}]
   (let [format*   (mf/use-state :hex)
         format    (deref format*)
-
         color     (shape->color shape)
         on-change
         (mf/use-fn
@@ -55,6 +54,7 @@
     (when (seq shapes)
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
+                      :origin      :inspect
                       :title       (tr "inspect.attributes.fill")
                       :class       (stl/css :title-spacing-fill)}]
 
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
index 3f3e3a9bd..9515dad3e 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.scss
@@ -13,3 +13,8 @@
 .title-spacing-fill {
   @extend .attr-title;
 }
+
+.attributes-content {
+  display: grid;
+  gap: $s-4;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
index 4735502f7..cc7cc70f5 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.cljs
@@ -11,6 +11,7 @@
    [app.common.data.macros :as dm]
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.main.ui.components.title-bar :refer [title-bar]]
+   [app.main.ui.viewer.inspect.attributes.common :as cmm]
    [app.util.code-gen.style-css :as css]
    [app.util.i18n :refer [tr]]
    [rumext.v2 :as mf]))
@@ -22,12 +23,14 @@
   [:*
    (for [[idx property] (d/enumerate properties)]
      (when-let [value (css/get-css-value objects shape property)]
-       [:div {:key (dm/str "block-" idx "-" (d/name property))
-              :class (stl/css :geometry-row)}
-        [:div {:class (stl/css :global/attr-label)} (d/name property)]
-        [:div {:class (stl/css :global/attr-value)}
-         [:& copy-button {:data (css/get-css-property objects shape property)}
-          [:div {:class (stl/css :button-children)} value]]]]))])
+       (let [property-name (cmm/get-css-rule-humanized property)]
+         [:div {:key (dm/str "block-" idx "-" (d/name property))
+                :title property-name
+                :class (stl/css :geometry-row)}
+          [:div {:class (stl/css :global/attr-label)} property-name]
+          [:div {:class (stl/css :global/attr-value)}
+           [:& copy-button {:data (css/get-css-property objects shape property)}
+            [:div {:class (stl/css :button-children)} value]]]])))])
 
 
 (mf/defc geometry-panel
@@ -35,10 +38,12 @@
   [:div {:class (stl/css :attributes-block)}
    [:& title-bar {:collapsable false
                   :title       (tr "inspect.attributes.size")
+                  :origin      :inspect
                   :class       (stl/css :title-spacing-geometry)}
 
     (when (= (count shapes) 1)
-      [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+      [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)
+                       :class (stl/css :copy-btn-title)}])]
 
    (for [shape shapes]
      [:& geometry-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
index 2b1f431fb..b32c0c103 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/geometry.scss
@@ -21,3 +21,7 @@
 .button-children {
   @extend .copy-button-children;
 }
+
+.copy-btn-title {
+  max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
index 4f22213db..093b6a657 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/image.cljs
@@ -23,7 +23,7 @@
   [{:keys [objects shapes]}]
   (for [shape (filter cfh/image-shape? shapes)]
     [:div {:class (stl/css :attributes-block)
-           :key (str "image-" (:id shape))}
+           :key   (str "image-" (:id shape))}
      [:div {:class (stl/css :image-wrapper)}
       [:img {:src (cf/resolve-file-media (-> shape :metadata))}]]
 
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
index e015aecd1..ecfb19e3b 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs
@@ -8,9 +8,11 @@
   (:require-macros [app.main.style :as stl])
   (:require
    [app.common.data :as d]
+   [app.common.data.macros :as dm]
    [app.common.types.shape.layout :as ctl]
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.main.ui.components.title-bar :refer [title-bar]]
+   [app.main.ui.viewer.inspect.attributes.common :as cmm]
    [app.util.code-gen.style-css :as css]
    [rumext.v2 :as mf]))
 
@@ -31,13 +33,16 @@
   [{:keys [objects shape]}]
   (for [property properties]
     (when-let [value (css/get-css-value objects shape property)]
-      [:div {:class (stl/css :layout-row)}
-       [:div {:title (d/name property)
-              :class (stl/css :global/attr-label)} (d/name property)]
-       [:div {:class (stl/css :global/attr-value)}
+      (let [property-name (cmm/get-css-rule-humanized property)]
+        [:div {:class (stl/css :layout-row)}
+         [:div {:title property-name
+                :key   (dm/str "layout-" (:id shape) "-" (d/name property))
+                :class (stl/css :global/attr-label)}
+          property-name]
+         [:div {:class (stl/css :global/attr-value)}
 
-        [:& copy-button {:data (css/get-css-property objects shape property)}
-         [:div {:class (stl/css :button-children)} value]]]])))
+          [:& copy-button {:data (css/get-css-property objects shape property)}
+           [:div {:class (stl/css :button-children)} value]]]]))))
 
 (mf/defc layout-panel
   [{:keys [objects shapes]}]
@@ -46,11 +51,13 @@
     (when (seq shapes)
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
+                      :origin      :inspect
                       :title       "Layout"
                       :class       (stl/css :title-spacing-layout)}
 
         (when (= (count shapes) 1)
-          [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+          [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)
+                           :class (stl/css :copy-btn-title)}])]
 
        (for [shape shapes]
          [:& layout-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
index e2a409c0f..8a84e1d98 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.scss
@@ -21,3 +21,7 @@
 .button-children {
   @extend .copy-button-children;
 }
+
+.copy-btn-title {
+  max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
index 14eaf7f93..4ba5a98a6 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.cljs
@@ -8,9 +8,11 @@
   (:require-macros [app.main.style :as stl])
   (:require
    [app.common.data :as d]
+   [app.common.data.macros :as dm]
    [app.common.types.shape.layout :as ctl]
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.main.ui.components.title-bar :refer [title-bar]]
+   [app.main.ui.viewer.inspect.attributes.common :as cmm]
    [app.util.code-gen.style-css :as css]
    [rumext.v2 :as mf]))
 
@@ -33,12 +35,14 @@
   [{:keys [objects shape]}]
   (for [property properties]
     (when-let [value (css/get-css-value objects shape property)]
-      [:div {:class (stl/css :layout-element-row)}
-       [:div {:class (stl/css :global/attr-label)} (d/name property)]
-       [:div {:class (stl/css :global/attr-value)}
+      (let [property-name (cmm/get-css-rule-humanized property)]
+        [:div {:class (stl/css :layout-element-row)
+               :key (dm/str "layout-element-" (:id shape) "-" (d/name property))}
+         [:div {:class (stl/css :global/attr-label)} property-name]
+         [:div {:class (stl/css :global/attr-value)}
 
-        [:& copy-button {:data (css/get-css-property objects shape property)}
-         [:div {:class (stl/css :button-children)} value]]]])))
+          [:& copy-button {:data (css/get-css-property objects shape property)}
+           [:div {:class (stl/css :button-children)} value]]]]))))
 
 (mf/defc layout-element-panel
   [{:keys [objects shapes]}]
@@ -67,7 +71,8 @@
                       :title       menu-title
                       :class       (stl/css :title-spacing-layout-element)}
         (when (= (count shapes) 1)
-          [:& copy-button {:data (css/get-shape-properties-css objects (first shapes) properties)}])]
+          [:& copy-button {:data  (css/get-shape-properties-css objects (first shapes) properties)
+                           :class (stl/css :copy-btn-title)}])]
 
        (for [shape shapes]
          [:& layout-element-block {:shape shape
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
index 6ef7e6cea..56b174dd5 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_element.scss
@@ -21,3 +21,7 @@
 .button-children {
   @extend .copy-button-children;
 }
+
+.copy-btn-title {
+  max-width: $s-28;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
index 586d4488a..c77ab51b3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/shadow.cljs
@@ -54,6 +54,7 @@
     (when (and (seq shapes) (> (count shapes) 0))
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
+                      :origin      :inspect
                       :title       (tr "inspect.attributes.shadow")
                       :class       (stl/css :title-spacing-shadow)}]
 
@@ -61,4 +62,5 @@
         (for [shape shapes]
           (for [shadow (:shadow shape)]
             [:& shadow-block {:shape shape
+                              :key   (dm/str "block-" (:id shape) "-shadow")
                               :shadow shadow}]))]])))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
index 9dd86a035..2ba125be3 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs
@@ -62,6 +62,7 @@
     (when (seq shapes)
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
+                      :origin      :inspect
                       :title       (tr "inspect.attributes.stroke")
                       :class       (stl/css :title-spacing-stroke)}]
 
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
index 86cb67104..fe0f59df4 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.scss
@@ -25,3 +25,8 @@
 .button-children {
   @extend .copy-button-children;
 }
+
+.attributes-content {
+  display: grid;
+  gap: $s-4;
+}
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
index 8df36110f..c788c95db 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/svg.cljs
@@ -27,19 +27,25 @@
       [:& copy-button {:data (map->css value)}]]
 
      (for [[attr-key attr-value] value]
-       [:& svg-attr {:attr  attr-key :value attr-value}])]
+       [:& svg-attr {:attr  attr-key :value attr-value :key (str/join "svg-key-" attr-key)}])]
 
-    [:div {:class (stl/css :svg-row)}
-     [:div {:class (stl/css :global/attr-label)} (d/name attr)]
-     [:div {:class (stl/css :global/attr-value)}
-      [:& copy-button {:data (d/name value)}
-       [:div {:class (stl/css :button-children)} (str value)]]]]))
+    (let [attr-name (as-> attr $
+                      (d/name $)
+                      (str/split $ "-")
+                      (str/join " " $)
+                      (str/capital $))]
+      [:div {:class (stl/css :svg-row)}
+       [:div {:class (stl/css :global/attr-label)} attr-name]
+       [:div {:class (stl/css :global/attr-value)}
+        [:& copy-button {:data  (d/name value)
+                         :class (stl/css :copy-btn-title)}
+         [:div {:class (stl/css :button-children)} (str value)]]]])))
 
 (mf/defc svg-block
   [{:keys [shape]}]
   [:*
    (for [[attr-key attr-value] (:svg-attrs shape)]
-     [:& svg-attr {:attr  attr-key :value attr-value}])])
+     [:& svg-attr {:attr  attr-key :value attr-value :key (str/join "svg-block-key" attr-key)}])])
 
 
 (mf/defc svg-panel
@@ -48,6 +54,7 @@
     (when (seq (:svg-attrs shape))
       [:div {:class (stl/css :attributes-block)}
        [:& title-bar {:collapsable false
+                      :origin      :inspect
                       :title       (tr "workspace.sidebar.options.svg-attrs.title")
                       :class       (stl/css :title-spacing-svg)}]
        [:& svg-block {:shape shape}]])))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
index f3f817397..137fcc01c 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs
@@ -99,7 +99,7 @@
         [:div {:class (stl/css :global/attr-value)}
          [:& copy-button {:data (copy-style-data style :font-style)}
           [:div {:class (stl/css :button-children)}
-           (str (:font-style style))]]]])
+           (dm/str (:font-style style))]]]])
 
      (when (:font-size style)
        [:div {:class (stl/css :text-row)}
@@ -117,7 +117,7 @@
         [:div {:class (stl/css :global/attr-value)}
          [:& copy-button {:data (copy-style-data style :font-weight)}
           [:div {:class (stl/css :button-children)}
-           (str (:font-weight style))]]]])
+           (dm/str (:font-weight style))]]]])
 
      (when (:line-height style)
        [:div {:class (stl/css :text-row)}
@@ -191,9 +191,10 @@
   (when-let [shapes (seq (filter has-text? shapes))]
     [:div {:class (stl/css :attributes-block)}
      [:& title-bar {:collapsable false
+                    :origin      :inspect
                     :title       (tr "inspect.attributes.typography")
                     :class       (stl/css :title-spacing-text)}]
 
      (for [shape shapes]
        [:& text-block {:shape shape
-                       :key (str "text-block" (:id shape))}])]))
+                       :key (dm/str "text-block" (:id shape))}])]))
diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss b/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
index a506af1b4..d32a5a516 100644
--- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.scss
@@ -30,7 +30,7 @@
 }
 
 .attributes-content-row {
-  max-width: $s-252;
+  max-width: $s-240;
   min-height: calc($s-2 + $s-32);
   border-radius: $br-8;
   border: $s-1 solid var(--menu-border-color-disabled);
diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs
index 03afee4ac..6baa45ef9 100644
--- a/frontend/src/app/main/ui/viewer/inspect/code.cljs
+++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs
@@ -19,7 +19,7 @@
    [app.main.store :as st]
    [app.main.ui.components.code-block :refer [code-block]]
    [app.main.ui.components.copy-button :refer [copy-button]]
-   [app.main.ui.components.select :refer [select]]
+   [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
    [app.main.ui.hooks :as hooks]
    [app.main.ui.hooks.resize :refer [use-resize-hook]]
    [app.main.ui.icons :as i]
@@ -177,10 +177,10 @@
          style-size :size}
         (use-resize-hook :code 400 100 800 :y false :bottom)
 
-        set-style
-        (mf/use-callback
-         (fn [value]
-           (reset! style-type* value)))
+        ;; set-style
+        ;; (mf/use-callback
+        ;;  (fn [value]
+        ;;    (reset! style-type* value)))
 
         set-markup
         (mf/use-callback
@@ -251,10 +251,13 @@
                         :rotated collapsed-css?)}
          i/arrow-refactor]]
 
-       [:& select {:default-value style-type
-                   :class (stl/css :code-lang-select)
-                   :on-change set-style
-                   :options [{:label "CSS" :value "css"}]}]
+       [:div {:class (stl/css :code-lang-option)}
+        "CSS"]
+      ;; We will have a select when we have more than one option
+      ;;  [:& select {:default-value style-type
+      ;;              :class (stl/css :code-lang-select)
+      ;;              :on-change set-style
+      ;;              :options [{:label "CSS" :value "css"}]}]
 
        [:div {:class (stl/css :action-btns)}
         [:button {:class (stl/css :expand-button)
@@ -262,6 +265,7 @@
          i/code-refactor]
 
         [:& copy-button {:data #(replace-map style-code images-data)
+                         :class (stl/css :css-copy-btn)
                          :on-copied on-style-copied}]]]
 
       (when-not collapsed-css?
@@ -285,11 +289,16 @@
                         :collapsabled-icon true
                         :rotated collapsed-markup?)}
          i/arrow-refactor]]
-       [:& select {:default-value markup-type
-                   :class (stl/css :code-lang-select)
-                   :options [{:label "HTML" :value "html"}
-                             {:label "SVG" :value "svg"}]
-                   :on-change set-markup}]
+
+       [:& radio-buttons {:selected markup-type
+                          :on-change set-markup
+                          :class (stl/css :code-lang-options)
+                          :wide true
+                          :name "listing-style"}
+        [:& radio-button {:value "html"
+                          :id :html}]
+        [:& radio-button {:value "svg"
+                          :id :svg}]]
 
        [:div {:class (stl/css :action-btns)}
         [:button {:class (stl/css :expand-button)
@@ -297,6 +306,7 @@
          i/code-refactor]
 
         [:& copy-button {:data #(replace-map markup-code images-data)
+                         :class (stl/css :html-copy-btn)
                          :on-copied on-markup-copied}]]]
 
       (when-not collapsed-markup?
diff --git a/frontend/src/app/main/ui/viewer/inspect/code.scss b/frontend/src/app/main/ui/viewer/inspect/code.scss
index 5b6d66c9c..a383684c7 100644
--- a/frontend/src/app/main/ui/viewer/inspect/code.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/code.scss
@@ -48,8 +48,8 @@
 }
 
 .code-row-lang {
-  display: flex;
-  justify-content: space-between;
+  display: grid;
+  grid-template-columns: $s-12 1fr $s-60;
   gap: $s-4;
   width: 100%;
 }
@@ -61,13 +61,14 @@
 }
 
 .action-btns {
-  display: flex;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
   gap: $s-4;
-  flex: 1;
-  justify-content: end;
 }
 
-.expand-button {
+.expand-button,
+.css-copy-btn,
+.html-copy-btn {
   @extend .button-tertiary;
   height: $s-32;
   width: $s-28;
@@ -77,6 +78,9 @@
   }
 }
 
+.code-lang-options {
+  max-width: $s-108;
+}
 .code-lang-select {
   @include tabTitleTipography;
   width: $s-72;
@@ -84,6 +88,13 @@
   background-color: transparent;
   color: var(--menu-foreground-color-disabled);
 }
+.code-lang-option {
+  @include tabTitleTipography;
+  width: $s-72;
+  height: $s-32;
+  padding: $s-8;
+  color: var(--menu-foreground-color-disabled);
+}
 
 .code-row-display {
   flex: 1;
diff --git a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
index b66e44ce7..5d9646ab6 100644
--- a/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
+++ b/frontend/src/app/main/ui/viewer/inspect/right_sidebar.scss
@@ -14,8 +14,6 @@
   left: unset;
   right: unset;
   grid-area: right-sidebar;
-  padding-top: $s-8;
-  padding-left: $s-12;
   overflow: hidden;
   &.viewer-code {
     height: calc(100vh - $s-48);
@@ -26,18 +24,20 @@
   height: 100%;
   display: flex;
   flex-direction: column;
+  gap: $s-8;
 }
 
 .shape-row {
   display: flex;
   gap: $s-8;
   align-items: center;
-  margin-bottom: $s-16;
+  height: $s-32;
 }
 
 .layers-icon,
 .shape-icon {
   @include flexCenter;
+  height: $s-32;
   svg {
     @extend .button-icon-small;
     stroke: var(--icon-foreground);
@@ -46,7 +46,9 @@
 
 .layer-title {
   @include titleTipography;
-  color: $df-primary;
+  height: $s-32;
+  padding: $s-8 0;
+  color: var(--assets-item-name-foreground-color-rest);
 }
 
 .empty {
diff --git a/frontend/src/app/main/ui/workspace/color_palette.cljs b/frontend/src/app/main/ui/workspace/color_palette.cljs
index ceaeebd9b..b07848671 100644
--- a/frontend/src/app/main/ui/workspace/color_palette.cljs
+++ b/frontend/src/app/main/ui/workspace/color_palette.cljs
@@ -31,7 +31,7 @@
            :title (uc/get-color-name color)
            :on-click select-color}
      [:& cb/color-bullet {:color color}]
-     [:& cb/color-name {:color color :size size}]]))
+     [:& cb/color-name {:color color :size size :origin :palette}]]))
 
 
 (mf/defc palette
diff --git a/frontend/src/app/main/ui/workspace/sidebar.scss b/frontend/src/app/main/ui/workspace/sidebar.scss
index b2ece935e..b14ca1253 100644
--- a/frontend/src/app/main/ui/workspace/sidebar.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar.scss
@@ -16,7 +16,6 @@ $width-settings-bar-max: $s-500;
     "content resize";
   grid-template-rows: $s-52 1fr;
   grid-template-columns: 1fr 0;
-  gap: $s-8 0;
   position: relative;
   grid-area: left-sidebar;
   min-width: $width-settings-bar;
@@ -85,10 +84,10 @@ $width-settings-bar-max: $s-500;
 
 .resize-area-horiz {
   position: absolute;
-  top: calc($s-80 + var(--height, 200px));
+  // top: calc($s-88 + var(--height, 200px));
   left: 0;
   width: 100%;
-  height: $s-12;
-  border-top: $s-2 solid var(--resize-area-border-color);
+  // height: $s-8;
+  border-bottom: $s-2 solid var(--resize-area-border-color);
   cursor: ns-resize;
 }
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.scss b/frontend/src/app/main/ui/workspace/sidebar/layers.scss
index 4b39fbaa4..25cf2cc4a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.scss
@@ -241,8 +241,7 @@
 }
 
 .tool-window-content {
-  // TODO: sass variables are not being interpolated here, find why
-  --calculated-height: calc(128px + var(--height, 200px));
+  --calculated-height: calc(#{$s-136} + var(--height, #{$s-200}));
   display: flex;
   flex-direction: column;
   height: calc(100vh - var(--calculated-height));
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 46f2e63c1..f26593180 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -5056,6 +5056,9 @@ msgstr "Done"
 msgid "media.image"
 msgstr "Image"
 
+msgid "media.image.short"
+msgstr "img"
+
 msgid "media.solid"
 msgstr "Solid"
 
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 8c8c0d4cc..4fe0c535e 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -5140,6 +5140,9 @@ msgstr "Hecho"
 msgid "media.image"
 msgstr "Imagen"
 
+msgid "media.image.short"
+msgstr "img"
+
 msgid "media.solid"
 msgstr "Sólido"
 

From 377d9682da629b8b73929081b17579c910810edd Mon Sep 17 00:00:00 2001
From: Alejandro Alonso <alejandroalonsofernandez@gmail.com>
Date: Tue, 13 Feb 2024 16:49:45 +0100
Subject: [PATCH 22/23] :bug: Fix default constraints for migrated graphics

---
 backend/src/app/features/components_v2.clj | 25 +++++++++++++---------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj
index 72f416bdf..67cf48b07 100644
--- a/backend/src/app/features/components_v2.clj
+++ b/backend/src/app/features/components_v2.clj
@@ -1187,15 +1187,18 @@
   "Convert a media object that contains a bitmap image into shapes,
   one shape of type :image and one group that contains it."
   [{:keys [name width height id mtype]} frame-id position]
-  (let [frame-shape (cts/setup-shape
-                     {:type :frame
-                      :x (:x position)
-                      :y (:y position)
-                      :width width
-                      :height height
-                      :name name
-                      :frame-id frame-id
-                      :parent-id frame-id})
+  (let [frame-shape (-> (cts/setup-shape
+                         {:type :frame
+                          :x (:x position)
+                          :y (:y position)
+                          :width width
+                          :height height
+                          :name name
+                          :frame-id frame-id
+                          :parent-id frame-id})
+                        (assoc
+                         :proportion (/ width height)
+                         :proportion-lock true))
 
         img-shape   (cts/setup-shape
                      {:type :image
@@ -1209,7 +1212,9 @@
                                  :mtype mtype}
                       :name name
                       :frame-id (:id frame-shape)
-                      :parent-id (:id frame-shape)})]
+                      :parent-id (:id frame-shape)
+                      :constraints-h :scale
+                      :constraints-v :scale})]
     [frame-shape [img-shape]]))
 
 (defn- parse-datauri

From ee8cdfc7d34daf0d965861fa0407f83e3d31e34c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Wed, 14 Feb 2024 11:37:05 +0100
Subject: [PATCH 23/23] :bug: Fix boolean flatten icon size

---
 .../workspace/sidebar/options/menus/bool.cljs |  7 +++--
 .../workspace/sidebar/options/menus/bool.scss | 26 +++++++++----------
 2 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
index 7a25e1bf5..c2baaf769 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
@@ -17,6 +17,9 @@
    [app.util.i18n :as i18n :refer [tr]]
    [rumext.v2 :as mf]))
 
+(def ^:private flatten-icon
+  (i/icon-xref :boolean-flatten-refactor (stl/css :flatten-icon)))
+
 (mf/defc bool-options
   []
   (let [selected               (mf/deref refs/selected-objects)
@@ -86,9 +89,9 @@
         [:button
          {:title (tr "workspace.shape.menu.flatten")
           :class (stl/css-case
-                  :flatten true
+                  :flatten-button true
                   :disabled disabled-flatten)
           :disabled disabled-flatten
           :on-click flatten-objects}
-         i/boolean-flatten-refactor]]])))
+         flatten-icon]]])))
 
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
index 625a80b71..bc26d5b8b 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss
@@ -18,29 +18,29 @@
   align-items: center;
 }
 
-.flatten {
+.flatten-button {
   @extend .button-tertiary;
-  height: $s-28;
-  width: $s-28;
+  height: $s-32;
+  width: $s-32;
   border-radius: $br-8;
-  svg {
-    @extend .button-icon;
-    stroke: var(--icon-foreground);
-  }
+  --flatten-icon-foreground-color: var(--icon-foreground);
+
   &.disabled {
     cursor: default;
-    svg {
-      stroke: var(--button-foreground-color-disabled);
-    }
+    --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
+
     &:hover {
       background-color: var(--panel-background-color);
-      svg {
-        stroke: var(--button-foreground-color-disabled);
-      }
+      --flatten-icon-foreground-color: var(--button-foreground-color-disabled);
     }
   }
 }
 
+.flatten-icon {
+  @extend .button-icon;
+  stroke: var(--flatten-icon-foreground-color);
+}
+
 .boolean-radio-btn {
   background-color: transparent;
 }