diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj
index 6e2e086bb..dd23992f7 100644
--- a/backend/src/app/db.clj
+++ b/backend/src/app/db.clj
@@ -231,9 +231,9 @@
 (defn get-by-params
   ([ds table params]
    (get-by-params ds table params nil))
-  ([ds table params {:keys [uncheked] :or {uncheked false} :as opts}]
+  ([ds table params {:keys [check-not-found] :or {check-not-found true} :as opts}]
    (let [res (exec-one! ds (sql/select table params opts))]
-     (when (and (not uncheked) (or (not res) (is-deleted? res)))
+     (when (and check-not-found (or (not res) (is-deleted? res)))
        (ex/raise :type :not-found
                  :table table
                  :hint "database object not found"))
@@ -267,13 +267,28 @@
   (instance? PGpoint v))
 
 (defn pgarray?
-  [v]
-  (instance? PgArray v))
+  ([v] (instance? PgArray v))
+  ([v type]
+   (and (instance? PgArray v)
+        (= type (.getBaseTypeName ^PgArray v)))))
 
 (defn pgarray-of-uuid?
   [v]
   (and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v))))
 
+(defn decode-pgarray
+  ([v] (into [] (.getArray ^PgArray v)))
+  ([v in] (into in (.getArray ^PgArray v)))
+  ([v in xf] (into in xf (.getArray ^PgArray v))))
+
+(defn pgarray->set
+  [v]
+  (set (.getArray ^PgArray v)))
+
+(defn pgarray->vector
+  [v]
+  (vec (.getArray ^PgArray v)))
+
 (defn pgpoint
   [p]
   (PGpoint. (:x p) (:y p)))
@@ -369,15 +384,6 @@
     (.setType "jsonb")
     (.setValue (json/encode-str data))))
 
-(defn pgarray->set
-  [v]
-  (set (.getArray ^PgArray v)))
-
-(defn pgarray->vector
-  [v]
-  (vec (.getArray ^PgArray v)))
-
-
 ;; --- Locks
 
 (defn- xact-check-param
diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj
index 42babf3dc..4a9929ec0 100644
--- a/backend/src/app/http/middleware.clj
+++ b/backend/src/app/http/middleware.clj
@@ -81,6 +81,9 @@
       (try
         (let [tw (t/writer output-stream opts)]
           (t/write! tw data))
+        (catch Throwable e
+          (l/error :hint "exception on writting data to response"
+                   :cause e))
         (finally
           (.close ^java.io.OutputStream output-stream))))))
 
diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj
index c0f62cf3f..831213383 100644
--- a/backend/src/app/migrations.clj
+++ b/backend/src/app/migrations.clj
@@ -193,6 +193,12 @@
 
    {:name "0061-mod-file-table"
     :fn (mg/resource "app/migrations/sql/0061-mod-file-table.sql")}
+
+   {:name "0062-fix-metadata-media"
+    :fn (mg/resource "app/migrations/sql/0062-fix-metadata-media.sql")}
+
+   {:name "0063-add-share-link-table"
+    :fn (mg/resource "app/migrations/sql/0063-add-share-link-table.sql")}
    ])
 
 
diff --git a/backend/src/app/migrations/sql/0063-add-share-link-table.sql b/backend/src/app/migrations/sql/0063-add-share-link-table.sql
new file mode 100644
index 000000000..6c2790045
--- /dev/null
+++ b/backend/src/app/migrations/sql/0063-add-share-link-table.sql
@@ -0,0 +1,12 @@
+CREATE TABLE share_link (
+  id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
+  file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE DEFERRABLE,
+  owner_id uuid NULL REFERENCES profile(id) ON DELETE SET NULL DEFERRABLE,
+  created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+
+  pages uuid[],
+  flags text[]
+);
+
+CREATE INDEX share_link_file_id_idx ON share_link(file_id);
+CREATE INDEX share_link_owner_id_idx ON share_link(owner_id);
diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj
index 09a2a636a..eae7ef33c 100644
--- a/backend/src/app/rpc.clj
+++ b/backend/src/app/rpc.clj
@@ -175,6 +175,7 @@
                      'app.rpc.mutations.management
                      'app.rpc.mutations.ldap
                      'app.rpc.mutations.fonts
+                     'app.rpc.mutations.share-link
                      'app.rpc.mutations.verify-token)
          (map (partial process-method cfg))
          (into {}))))
diff --git a/backend/src/app/rpc/mutations/share_link.clj b/backend/src/app/rpc/mutations/share_link.clj
new file mode 100644
index 000000000..0e366957f
--- /dev/null
+++ b/backend/src/app/rpc/mutations/share_link.clj
@@ -0,0 +1,67 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.rpc.mutations.share-link
+  "Share link related rpc mutation methods."
+  (:require
+   [app.common.spec :as us]
+   [app.common.uuid :as uuid]
+   [app.db :as db]
+   [app.rpc.queries.files :as files]
+   [app.util.services :as sv]
+   [clojure.spec.alpha :as s]))
+
+;; --- Helpers & Specs
+
+(s/def ::id ::us/uuid)
+(s/def ::profile-id ::us/uuid)
+(s/def ::file-id ::us/uuid)
+(s/def ::flags (s/every ::us/string :kind set?))
+(s/def ::pages (s/every ::us/uuid :kind set?))
+
+;; --- Mutation: Create Share Link
+
+(declare create-share-link)
+
+(s/def ::create-share-link
+  (s/keys :req-un [::profile-id ::file-id ::flags]
+          :opt-un [::pages]))
+
+(sv/defmethod ::create-share-link
+  [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
+  (db/with-atomic [conn pool]
+    (files/check-edition-permissions! conn profile-id file-id)
+    (create-share-link conn params)))
+
+(defn create-share-link
+  [conn {:keys [profile-id file-id pages flags]}]
+  (let [pages (db/create-array conn "uuid" pages)
+        flags (->> (map name flags)
+                   (db/create-array conn "text"))
+        slink (db/insert! conn :share-link
+                          {:id (uuid/next)
+                           :file-id file-id
+                           :flags flags
+                           :pages pages
+                           :owner-id profile-id})]
+    (-> slink
+        (update :pages db/decode-pgarray #{})
+        (update :flags db/decode-pgarray #{}))))
+
+;; --- Mutation: Delete Share Link
+
+(declare delete-share-link)
+
+(s/def ::delete-share-link
+  (s/keys :req-un [::profile-id ::id]))
+
+(sv/defmethod ::delete-share-link
+  [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
+  (db/with-atomic [conn pool]
+    (let [slink (db/get-by-id conn :share-link id)]
+      (files/check-edition-permissions! conn profile-id (:file-id slink))
+      (db/delete! conn :share-link {:id id})
+      nil)))
diff --git a/backend/src/app/rpc/permissions.clj b/backend/src/app/rpc/permissions.clj
index 221b88363..9481bcd57 100644
--- a/backend/src/app/rpc/permissions.clj
+++ b/backend/src/app/rpc/permissions.clj
@@ -37,6 +37,41 @@
            :is-admin false
            :can-edit false)))
 
+(defn make-edition-predicate-fn
+  "A simple factory for edition permission predicate functions."
+  [qfn]
+  (us/assert fn? qfn)
+  (fn [& args]
+    (let [rows (apply qfn args)]
+      (when-not (or (empty? rows)
+                    (not (or (some :can-edit rows)
+                             (some :is-admin rows)
+                             (some :is-owner rows))))
+        rows))))
+
+(defn make-read-predicate-fn
+  "A simple factory for read permission predicate functions."
+  [qfn]
+  (us/assert fn? qfn)
+  (fn [& args]
+    (let [rows (apply qfn args)]
+      (when (seq rows)
+        rows))))
+
+(defn make-check-fn
+  "Helper that converts a predicate permission function to a check
+  function (function that raises an exception)."
+  [pred]
+  (fn [& args]
+    (when-not (seq (apply pred args))
+      (ex/raise :type :not-found
+                :code :object-not-found
+                :hint "not found"))))
+
+
+;; TODO: the following functions are deprecated and replaced with the
+;; new ones. Should not be used.
+
 (defn make-edition-check-fn
   "A simple factory for edition permission check functions."
   [qfn]
diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj
index 55f536d5f..032032216 100644
--- a/backend/src/app/rpc/queries/files.clj
+++ b/backend/src/app/rpc/queries/files.clj
@@ -61,16 +61,23 @@
 
 (defn- retrieve-file-permissions
   [conn profile-id file-id]
-  (db/exec! conn [sql:file-permissions
-                  file-id profile-id
-                  file-id profile-id
-                  file-id profile-id]))
+  (when (and profile-id file-id)
+    (db/exec! conn [sql:file-permissions
+                    file-id profile-id
+                    file-id profile-id
+                    file-id profile-id])))
+
+(def has-edit-permissions?
+  (perms/make-edition-predicate-fn retrieve-file-permissions))
+
+(def has-read-permissions?
+  (perms/make-read-predicate-fn retrieve-file-permissions))
 
 (def check-edition-permissions!
-  (perms/make-edition-check-fn retrieve-file-permissions))
+  (perms/make-check-fn has-edit-permissions?))
 
 (def check-read-permissions!
-  (perms/make-read-check-fn retrieve-file-permissions))
+  (perms/make-check-fn has-read-permissions?))
 
 
 ;; --- Query: Files search
diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj
index dfe95314c..bddae363c 100644
--- a/backend/src/app/rpc/queries/viewer.clj
+++ b/backend/src/app/rpc/queries/viewer.clj
@@ -14,24 +14,97 @@
    [app.util.services :as sv]
    [clojure.spec.alpha :as s]))
 
+;; --- Query: View Only Bundle
+
+(defn- decode-share-link-row
+  [row]
+  (-> row
+      (update :flags db/decode-pgarray #{})
+      (update :pages db/decode-pgarray #{})))
+
+(defn- retrieve-project
+  [conn id]
+  (db/get-by-id conn :project id {:columns [:id :name :team-id]}))
+
+(defn- retrieve-share-link
+  [{:keys [conn]} file-id id]
+  (some-> (db/get-by-params conn :share-link
+                            {:id id :file-id file-id}
+                            {:check-not-found false})
+          (decode-share-link-row)))
+
+(defn- retrieve-bundle
+  [{:keys [conn] :as cfg} file-id]
+  (let [file    (files/retrieve-file cfg file-id)
+        project (retrieve-project conn (:project-id file))
+        libs    (files/retrieve-file-libraries cfg false file-id)
+        users   (teams/retrieve-users conn (:team-id project))
+
+        links   (->> (db/query conn :share-link {:file-id file-id})
+                     (mapv decode-share-link-row))
+
+        fonts   (db/query conn :team-font-variant
+                          {:team-id (:team-id project)
+                           :deleted-at nil})]
+    {:file file
+     :users users
+     :fonts fonts
+     :project project
+     :share-links links
+     :libraries libs}))
+
+(s/def ::file-id ::us/uuid)
+(s/def ::profile-id ::us/uuid)
+(s/def ::share-id ::us/uuid)
+
+(s/def ::view-only-bundle
+  (s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id]))
+
+(sv/defmethod ::view-only-bundle {:auth false}
+  [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
+  (db/with-atomic [conn pool]
+    (let [cfg    (assoc cfg :conn conn)
+          bundle (retrieve-bundle cfg file-id)
+          slink  (retrieve-share-link cfg file-id share-id)]
+
+      ;; When we have neither profile nor share, we just return a not
+      ;; found response to the user.
+      (when (and (not profile-id)
+                 (not slink))
+        (ex/raise :type :not-found
+                  :code :object-not-found))
+
+      ;; When we have only profile, we need to check read permissiones
+      ;; on file.
+      (when (and profile-id (not slink))
+        (files/check-read-permissions! conn profile-id file-id))
+
+      (cond-> bundle
+        ;; If we have current profile, put
+        (some? profile-id)
+        (as-> $ (let [edit? (boolean (files/has-edit-permissions? conn profile-id file-id))
+                      read? (boolean (files/has-read-permissions? conn profile-id file-id))]
+                  (-> (assoc $ :permissions {:read read? :edit edit?})
+                      (cond-> (not edit?) (dissoc :share-links)))))
+
+        (some? slink)
+        (assoc :share slink)
+
+        (not (contains? (:flags slink) "view-all-pages"))
+        (update-in [:file :data] (fn [data]
+                                   (let [allowed-pages (:pages slink)]
+                                     (-> data
+                                         (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages)))
+                                         (update :pages-index (fn [index] (select-keys index allowed-pages)))))))))))
+
 ;; --- Query: Viewer Bundle (by Page ID)
 
+;; DEPRECATED: should be removed in 1.9.x
+
 (declare check-shared-token!)
 (declare retrieve-shared-token)
 
-(def ^:private
-  sql:project
-  "select p.id, p.name, p.team_id
-     from project as p
-    where p.id = ?
-      and p.deleted_at is null")
-
-(defn- retrieve-project
-  [conn id]
-  (db/exec-one! conn [sql:project id]))
-
 (s/def ::id ::us/uuid)
-(s/def ::file-id ::us/uuid)
 (s/def ::page-id ::us/uuid)
 (s/def ::token ::us/string)
 
@@ -81,6 +154,3 @@
   [conn file-id page-id]
   (let [sql "select * from file_share_token where file_id=? and page_id=?"]
     (db/exec-one! conn [sql file-id page-id])))
-
-
-
diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj
index 2a30a6324..b1b3e701b 100644
--- a/backend/src/app/tasks/delete_object.clj
+++ b/backend/src/app/tasks/delete_object.clj
@@ -60,7 +60,7 @@
 
 (defmethod handle-deletion :team-font-variant
   [{:keys [conn storage]} {:keys [id] :as props}]
-  (let [font    (db/get-by-id conn :team-font-variant id {:uncheked true})
+  (let [font    (db/get-by-id conn :team-font-variant id {:check-not-found false})
         storage (assoc storage :conn conn)]
     (when (:deleted-at font)
       (db/delete! conn :team-font-variant {:id id})
diff --git a/backend/test/app/services_viewer_test.clj b/backend/test/app/services_viewer_test.clj
index d3176f81a..1b9d7c013 100644
--- a/backend/test/app/services_viewer_test.clj
+++ b/backend/test/app/services_viewer_test.clj
@@ -16,18 +16,18 @@
 (t/use-fixtures :each th/database-reset)
 
 (t/deftest retrieve-bundle
-  (let [prof    (th/create-profile* 1 {:is-active true})
-        prof2   (th/create-profile* 2 {:is-active true})
-        team-id (:default-team-id prof)
-        proj-id (:default-project-id prof)
+  (let [prof     (th/create-profile* 1 {:is-active true})
+        prof2    (th/create-profile* 2 {:is-active true})
+        team-id  (:default-team-id prof)
+        proj-id  (:default-project-id prof)
 
-        file    (th/create-file* 1 {:profile-id (:id prof)
-                                    :project-id proj-id
-                                    :is-shared false})
-        token   (atom nil)]
+        file     (th/create-file* 1 {:profile-id (:id prof)
+                                     :project-id proj-id
+                                     :is-shared false})
+        share-id (atom nil)]
 
     (t/testing "authenticated with page-id"
-      (let [data {::th/type :viewer-bundle
+      (let [data {::th/type :view-only-bundle
                   :profile-id (:id prof)
                   :file-id (:id file)
                   :page-id (get-in file [:data :pages 0])}
@@ -38,64 +38,67 @@
         (t/is (nil? (:error out)))
 
         (let [result (:result out)]
-          (t/is (contains? result :token))
-          (t/is (contains? result :page))
+          (t/is (contains? result :share-links))
+          (t/is (contains? result :permissions))
+          (t/is (contains? result :libraries))
           (t/is (contains? result :file))
           (t/is (contains? result :project)))))
 
     (t/testing "generate share token"
-      (let [data {::th/type :create-file-share-token
+      (let [data {::th/type :create-share-link
                   :profile-id (:id prof)
                   :file-id (:id file)
-                  :page-id (get-in file [:data :pages 0])}
+                  :pages #{(get-in file [:data :pages 0])}
+                  :flags #{}}
             out  (th/mutation! data)]
 
         ;; (th/print-result! out)
         (t/is (nil? (:error out)))
         (let [result (:result out)]
-          (t/is (string? (:token result)))
-          (reset! token (:token result)))))
+          (t/is (uuid? (:id result)))
+          (reset! share-id (:id result)))))
 
     (t/testing "not authenticated with page-id"
-      (let [data {::th/type :viewer-bundle
+      (let [data {::th/type :view-only-bundle
                   :profile-id (:id prof2)
                   :file-id (:id file)
                   :page-id (get-in file [:data :pages 0])}
             out  (th/query! data)]
 
         ;; (th/print-result! out)
-        (let [error (:error out)
+        (let [error      (:error out)
               error-data (ex-data error)]
           (t/is (th/ex-info? error))
           (t/is (= (:type error-data) :not-found))
           (t/is (= (:code error-data) :object-not-found)))))
 
-    ;; (t/testing "authenticated with token & profile"
-    ;;   (let [data {::sq/type :viewer-bundle
-    ;;               :profile-id (:id prof2)
-    ;;               :token @token
-    ;;               :file-id (:id file)
-    ;;               :page-id (get-in file [:data :pages 0])}
-    ;;         out  (th/try-on! (sq/handle data))]
+    (t/testing "authenticated with token & profile"
+      (let [data {::th/type :view-only-bundle
+                  :profile-id (:id prof2)
+                  :share-id @share-id
+                  :file-id (:id file)
+                  :page-id (get-in file [:data :pages 0])}
+            out  (th/query! data)]
 
-    ;;     ;; (th/print-result! out)
+        ;; (th/print-result! out)
+        (t/is (nil? (:error out)))
 
-    ;;     (let [result (:result out)]
-    ;;       (t/is (contains? result :page))
-    ;;       (t/is (contains? result :file))
-    ;;       (t/is (contains? result :project)))))
+        (let [result (:result out)]
+          (t/is (contains? result :share))
+          (t/is (contains? result :file))
+          (t/is (contains? result :project)))))
 
-    ;; (t/testing "authenticated with token"
-    ;;   (let [data {::sq/type :viewer-bundle
-    ;;               :token @token
-    ;;               :file-id (:id file)
-    ;;               :page-id (get-in file [:data :pages 0])}
-    ;;         out  (th/try-on! (sq/handle data))]
+    (t/testing "authenticated with token"
+      (let [data {::th/type :view-only-bundle
+                  :share-id @share-id
+                  :file-id (:id file)
+                  :page-id (get-in file [:data :pages 0])}
+            out  (th/query! data)]
 
-    ;;     ;; (th/print-result! out)
+        ;; (th/print-result! out)
+        (let [result (:result out)]
+          (t/is (contains? result :file))
+          (t/is (contains? result :share))
+          (t/is (contains? result :project)))))
 
-    ;;     (let [result (:result out)]
-    ;;       (t/is (contains? result :page))
-    ;;       (t/is (contains? result :file))
-    ;;       (t/is (contains? result :project)))))
     ))
diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss
index 6e0731217..3e76b8e62 100644
--- a/frontend/resources/styles/common/framework.scss
+++ b/frontend/resources/styles/common/framework.scss
@@ -131,6 +131,24 @@
   }
 }
 
+.btn-text-dark {
+  @extend %btn;
+  background: $color-gray-60;
+  color: $color-gray-20;
+
+  svg {
+    fill: $color-gray-20;
+  }
+  &:hover {
+    background: $color-primary;
+    color: $color-gray-60;
+    svg {
+      fill: $color-gray-60;
+    }
+  }
+}
+
+
 .btn-gray {
   @extend %btn;
   background: $color-gray-30;
@@ -588,7 +606,6 @@ input.element-name {
       box-sizing: border-box;
       flex-shrink: 0;
     }
-
   }
 
   &.column {
diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss
index 27c7c3610..5801d9c0b 100644
--- a/frontend/resources/styles/main-default.scss
+++ b/frontend/resources/styles/main-default.scss
@@ -88,3 +88,4 @@
 @import "main/partials/color-bullet";
 @import "main/partials/handoff";
 @import "main/partials/exception-page";
+@import "main/partials/share-link";
diff --git a/frontend/resources/styles/main/partials/dropdown.scss b/frontend/resources/styles/main/partials/dropdown.scss
index 0802e04d2..27500670a 100644
--- a/frontend/resources/styles/main/partials/dropdown.scss
+++ b/frontend/resources/styles/main/partials/dropdown.scss
@@ -53,8 +53,8 @@
     .icon {
       display: flex;
       align-items: center;
-      width: 25px;
-      height: 25px;
+      width: 20px;
+      height: 20px;
       margin-right: 7px;
     }
   }
diff --git a/frontend/resources/styles/main/partials/share-link.scss b/frontend/resources/styles/main/partials/share-link.scss
new file mode 100644
index 000000000..a632d8509
--- /dev/null
+++ b/frontend/resources/styles/main/partials/share-link.scss
@@ -0,0 +1,135 @@
+.share-link-dialog {
+  width: 475px;
+
+  .modal-footer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    height: unset;
+    padding: 16px 26px;
+
+    .btn-primary,
+    .btn-secondary,
+    .btn-warning {
+      width: 126px;
+      margin-bottom: 0px;
+
+      &:not(:last-child) {
+        margin-right: 10px;
+      }
+    }
+
+    .confirm-dialog {
+      display: flex;
+      flex-direction: column;
+      background-color: unset;
+
+      .description {
+        font-size: $fs14;
+
+        margin-bottom: 16px;
+
+      }
+      .actions {
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+
+
+  .modal-content {
+    padding: 26px;
+
+    .title {
+      display: flex;
+      justify-content: space-between;
+
+      h2 {
+        font-size: $fs18;
+        color: $color-black;
+      }
+
+      .modal-close-button {
+        margin-right: 0px;
+      }
+    }
+
+
+    .share-link-section {
+      margin-top: 12px;
+      label {
+        font-size: $fs11;
+        color: $color-black;
+      }
+
+      .hint {
+        padding-top: 10px;
+        font-size: $fs11;
+      }
+
+      .help-icon {
+        cursor: pointer;
+      }
+    }
+
+    .view-mode,
+    .access-mode {
+      display: flex;
+      flex-direction: column;
+
+      .title {
+        color: $color-black;
+        font-weight: 400;
+      }
+
+      .items {
+        padding-left: 20px;
+        display: flex;
+
+        > .input-checkbox, > .input-radio {
+          display: flex;
+          user-select: none;
+
+          /* input { */
+          /*   appearance: checkbox; */
+          /* } */
+
+          label {
+            display: flex;
+            align-items: center;
+            color: $color-black;
+
+            .hint {
+              margin-left: 5px;
+              color: $color-gray-30;
+            }
+          }
+
+          &.disabled {
+            label {
+              color: $color-gray-30;
+            }
+          }
+        }
+      }
+    }
+
+    .pages-selection {
+      padding-left: 20px;
+      max-height: 200px;
+      overflow-y: scroll;
+      user-select: none;
+
+      label {
+        color: $color-black;
+      }
+    }
+
+    .custom-input {
+      input {
+        padding: 0 40px 0 15px;
+      }
+    }
+  }
+}
diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss
index 605a08ec3..7aa002565 100644
--- a/frontend/resources/styles/main/partials/viewer-header.scss
+++ b/frontend/resources/styles/main/partials/viewer-header.scss
@@ -42,56 +42,64 @@
     }
   }
 
-  .view-options {
-    .icon {
-      align-items: center;
-      cursor: pointer;
-      display: flex;
-      justify-content: center;
+  .options-zone {
+    align-items: center;
+    display: flex;
+    // width: 384px;
+    justify-content: flex-end;
+    position: relative;
 
-      svg {
-        fill: $color-gray-30;
-        height: 30px;
-        width: 28px;
-      }
+    > * {
+      margin-left: $big;
+    }
 
-      &:hover {
-        > svg {
-          fill: $color-primary;
-        }
+    .btn-primary {
+      flex-shrink: 0;
+    }
+
+    .zoom-widget {
+      .dropdown {
+        top: 45px;
+        left: 25px;
       }
     }
 
-    .dropdown {
-      min-width: 260px;
-      left: 0px;
-      top: 40px;
-    }
-
-    .view-options-dropdown {
+    .view-options {
       align-items: center;
       cursor: pointer;
       display: flex;
+      width: 90px;
 
-      span {
+      > span {
         color: $color-gray-10;
         font-size: $fs13;
         margin-right: $x-small;
       }
 
-      svg {
-        fill: $color-gray-10;
-        height: 12px;
-        width: 12px;
-      }
-    }
-  }
+      > .icon {
+        align-items: center;
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
 
-  .file-menu {
-    .dropdown {
-      min-width: 100px;
-      right: 0px;
-      top: 40px;
+        svg {
+          fill: $color-gray-10;
+          height: 12px;
+          width: 12px;
+        }
+
+        &:hover {
+          > svg {
+            fill: $color-primary;
+          }
+        }
+      }
+
+      .dropdown {
+        min-width: 260px;
+        top: 45px;
+        left: -25px;
+      }
     }
   }
 
@@ -100,39 +108,46 @@
     cursor: pointer;
     display: flex;
     padding: $x-small;
+    position: relative;
 
-    svg {
-      fill: $color-gray-20;
-      height: 20px;
-      margin-right: $small;
-      width: 20px;
-    }
+    .icon {
+      display: flex;
+      justify-content: center;
+      align-items: center;
 
-    span {
-      color: $color-gray-20;
-      margin-right: $x-small;
-      font-size: $fs14;
-      overflow-x: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-
-      &.frame-name {
-        color: $color-white;
+      svg {
+        fill: $color-gray-20;
+        height: 12px;
+        margin-right: $small;
+        width: 12px;
       }
     }
 
-    .show-thumbnails-button svg {
-      fill: $color-white;
-      height: 10px;
-      width: 10px;
+    .breadcrumb {
+      display: flex;
+      position: relative;
+
+      > span {
+        color: $color-gray-20;
+        margin-right: $x-small;
+        font-size: $fs14;
+        overflow-x: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      > .dropdown {
+        top: 45px;
+        right: 10px;
+      }
     }
 
-    .page-name {
-      color: $color-white;
-    }
-
-    .counters {
-      margin-left: $size-3;
+    .current-frame {
+      display: flex;
+      span {
+        color: $color-white;
+        margin-right: $x-small;
+      }
     }
   }
 
@@ -166,133 +181,6 @@
     }
   }
 
-  .options-zone {
-    align-items: center;
-    display: flex;
-    width: 384px;
-    justify-content: flex-end;
-    position: relative;
-
-    > * {
-      margin-left: $big;
-    }
-
-    .btn-share {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 25px;
-      height: 25px;
-      cursor: pointer;
-
-      svg {
-        fill: $color-gray-20;
-        width: 20px;
-        height: 20px;
-      }
-    }
-
-    .btn-primary {
-      flex-shrink: 0;
-    }
-  }
-
-  .share-link-dropdown {
-    background-color: $color-white;
-    border-radius: $br-small;
-    box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
-    display: flex;
-    flex-direction: column;
-    left: -135px;
-    position: absolute;
-    padding: 1rem;
-    top: 45px;
-    width: 400px;
-
-    .share-link-title {
-      color: $color-black;
-      font-size: $fs15;
-      padding-bottom: 1rem;
-    }
-
-    .share-link-subtitle {
-      color: $color-gray-40;
-      padding-bottom: 1rem;
-    }
-
-    .share-link-buttons {
-      display: flex;
-      justify-content: center;
-      align-items: center;
-
-      .btn-warning,
-      .btn-primary {
-        width: 50%;
-      }
-
-    }
-
-    .share-link-input {
-      border: 1px solid $color-gray-20;
-      border-radius: 3px;
-      display: flex;
-      height: 40px;
-      justify-content: space-between;
-      margin-bottom: 1rem;
-      padding: 9px $small;
-      overflow: hidden;
-
-      .link {
-        &:before {
-          content: '';
-          position: absolute;
-          width: 50%;
-          background: linear-gradient(45deg, transparent, #ffffff);
-          height: 100%;
-          top: 0;
-          left: 0;
-          pointer-events: none;
-          margin-left: 50%;
-        }
-        overflow: hidden;
-        white-space: nowrap;
-        position: relative;
-        color: $color-gray-50;
-        line-height: 1.5;
-        user-select: all;
-        overflow: hidden;
-      }
-
-      .link-button {
-        color: $color-primary-dark;
-        cursor: pointer;
-        flex-shrink: 0;
-        font-size: $fs15;
-
-        &:hover {
-          color: $color-black;
-        }
-      }
-    }
-
-    &:before {
-      background-color: $color-white;
-      content: "";
-      height: 16px;
-      left: 53%;
-      position: absolute;
-      transform: rotate(45deg);
-      top: -5px;
-      width: 16px;
-    }
-
-  }
-
-  .zoom-dropdown {
-    left: 180px;
-    top: 40px;
-  }
-
   .users-zone {
     align-items: center;
     cursor: pointer;
diff --git a/frontend/resources/styles/main/partials/viewer-thumbnails.scss b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
index d630c8bfc..b60a92847 100644
--- a/frontend/resources/styles/main/partials/viewer-thumbnails.scss
+++ b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
@@ -1,4 +1,3 @@
-
 .viewer-thumbnails {
   grid-row: 1 / span 1;
   grid-column:  1 / span 1;
@@ -9,6 +8,11 @@
   flex-direction: column;
   z-index: 12;
 
+  &.invisible {
+    visibility: hidden;
+    pointer-events: none;
+  }
+
   &.expanded {
     grid-row: 1 / span 2;
 
@@ -159,7 +163,7 @@
 
     &:hover {
       border-color: $color-primary;
-      border-width: 2px;
+      outline: 2px solid $color-primary;
     }
   }
 
diff --git a/frontend/resources/styles/main/partials/zoom-widget.scss b/frontend/resources/styles/main/partials/zoom-widget.scss
index 4e68d8807..b25192321 100644
--- a/frontend/resources/styles/main/partials/zoom-widget.scss
+++ b/frontend/resources/styles/main/partials/zoom-widget.scss
@@ -8,13 +8,13 @@
     margin-left: $x-small;
   }
 
-  .dropdown-button svg {
+  .icon svg {
     fill: $color-gray-10;
     height: 10px;
     width: 10px;
   }
 
-  .zoom-dropdown {
+  .dropdown {
     position: absolute;
     z-index: 12;
     width: 210px;
diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs
new file mode 100644
index 000000000..f4a302650
--- /dev/null
+++ b/frontend/src/app/main/data/common.cljs
@@ -0,0 +1,46 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.common
+  "A general purpose events."
+  (:require
+   [app.main.repo :as rp]
+   [beicon.core :as rx]
+   [potok.core :as ptk]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; SHARE LINK
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn share-link-created
+  [link]
+  (ptk/reify ::share-link-created
+    ptk/UpdateEvent
+    (update [_ state]
+      (update state :share-links (fnil conj []) link))))
+
+(defn create-share-link
+  [params]
+  (ptk/reify ::create-share-link
+    ptk/WatchEvent
+    (watch [_ _ _]
+      (->> (rp/mutation! :create-share-link params)
+           (rx/map share-link-created)))))
+
+(defn delete-share-link
+  [{:keys [id] :as link}]
+  (ptk/reify ::delete-share-link
+    ptk/UpdateEvent
+    (update [_ state]
+      (update state :share-links
+              (fn [links]
+                (filterv #(not= id (:id %)) links))))
+
+    ptk/WatchEvent
+    (watch [_ _ _]
+      (->> (rp/mutation! :delete-share-link {:id id})
+           (rx/ignore)))))
+
diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs
index c6dc46a37..d73b6f478 100644
--- a/frontend/src/app/main/data/viewer.cljs
+++ b/frontend/src/app/main/data/viewer.cljs
@@ -14,24 +14,12 @@
    [app.main.data.comments :as dcm]
    [app.main.data.fonts :as df]
    [app.main.repo :as rp]
+   [app.util.globals :as ug]
    [app.util.router :as rt]
    [beicon.core :as rx]
    [cljs.spec.alpha :as s]
    [potok.core :as ptk]))
 
-;; --- General Specs
-
-(s/def ::id ::us/uuid)
-(s/def ::name ::us/string)
-
-(s/def ::project (s/keys :req-un [::id ::name]))
-(s/def ::file (s/keys :req-un [::id ::name]))
-(s/def ::page ::cp/page)
-
-(s/def ::bundle
-  (s/keys :req-un [::project ::file ::page]))
-
-
 ;; --- Local State Initialization
 
 (def ^:private
@@ -49,25 +37,24 @@
 (declare fetch-bundle)
 (declare bundle-fetched)
 
-(s/def ::page-id ::us/uuid)
 (s/def ::file-id ::us/uuid)
 (s/def ::index ::us/integer)
-(s/def ::token (s/nilable ::us/string))
+(s/def ::page-id (s/nilable ::us/uuid))
+(s/def ::share-id (s/nilable ::us/uuid))
 (s/def ::section ::us/string)
 
 (s/def ::initialize-params
-  (s/keys :req-un [::page-id ::file-id]
-          :opt-un [::token]))
+  (s/keys :req-un [::file-id]
+          :opt-un [::share-id ::page-id]))
 
 (defn initialize
-  [{:keys [page-id file-id] :as params}]
+  [{:keys [file-id] :as params}]
   (us/assert ::initialize-params params)
   (ptk/reify ::initialize
     ptk/UpdateEvent
     (update [_ state]
       (-> state
           (assoc :current-file-id file-id)
-          (assoc :current-page-id page-id)
           (update :viewer-local
                   (fn [lstate]
                     (if (nil? lstate)
@@ -77,55 +64,72 @@
     ptk/WatchEvent
     (watch [_ _ _]
       (rx/of (fetch-bundle params)
-             (fetch-comment-threads params)))))
+             (fetch-comment-threads params)))
 
-;; --- Data Fetching
+    ptk/EffectEvent
+    (effect [_ _ _]
+      ;; Set the window name, the window name is used on inter-tab
+      ;; navigation; in other words: when a user opens a tab with a
+      ;; name, if there are already opened tab with that name, the
+      ;; browser just focus the opened tab instead of creating new
+      ;; tab.
+      (let [name (str "viewer-" file-id)]
+        (unchecked-set ug/global "name" name)))))
 
-(s/def ::fetch-bundle-params
-  (s/keys :req-un [::page-id ::file-id]
-          :opt-un [::token]))
+(defn finalize
+  [_]
+  (ptk/reify ::finalize
+    ptk/UpdateEvent
+    (update [_ state]
+      (dissoc state :viewer))))
 
-(defn fetch-bundle
-  [{:keys [page-id file-id token] :as params}]
-  (us/assert ::fetch-bundle-params params)
-  (ptk/reify ::fetch-file
-    ptk/WatchEvent
-    (watch [_ _ _]
-      (let [params (cond-> {:page-id page-id
-                            :file-id file-id}
-                     (string? token) (assoc :token token))]
-        (->> (rp/query :viewer-bundle params)
-             (rx/mapcat
-              (fn [{:keys [fonts] :as bundle}]
-                (rx/of (df/fonts-fetched fonts)
-                       (bundle-fetched bundle)))))))))
-
-(defn- extract-frames
-  [objects]
+(defn select-frames
+  [{:keys [objects] :as page}]
   (let [root (get objects uuid/zero)]
     (into [] (comp (map #(get objects %))
                    (filter #(= :frame (:type %))))
           (reverse (:shapes root)))))
 
+;; --- Data Fetching
+
+(s/def ::fetch-bundle-params
+  (s/keys :req-un [::page-id ::file-id]
+          :opt-un [::share-id]))
+
+(defn fetch-bundle
+  [{:keys [file-id share-id] :as params}]
+  (us/assert ::fetch-bundle-params params)
+  (ptk/reify ::fetch-file
+    ptk/WatchEvent
+    (watch [_ _ _]
+      (let [params' (cond-> {:file-id file-id}
+                      (uuid? share-id) (assoc :share-id share-id))]
+        (->> (rp/query :view-only-bundle params')
+             (rx/mapcat
+              (fn [{:keys [fonts] :as bundle}]
+                (rx/of (df/fonts-fetched fonts)
+                       (bundle-fetched (merge bundle params))))))))))
+
+
 (defn bundle-fetched
-  [{:keys [project file page share-token token libraries users] :as bundle}]
-  (us/verify ::bundle bundle)
-  (ptk/reify ::bundle-fetched
-    ptk/UpdateEvent
-    (update [_ state]
-      (let [objects (:objects page)
-            frames  (extract-frames objects)]
+  [{:keys [project file share-links libraries users permissions] :as bundle}]
+  (let [pages (->> (get-in file [:data :pages])
+                   (map (fn [page-id]
+                          (let [data (get-in file [:data :pages-index page-id])]
+                            [page-id (assoc data :frames (select-frames data))])))
+                   (into {}))]
+
+    (ptk/reify ::bundle-fetched
+      ptk/UpdateEvent
+      (update [_ state]
         (-> state
-            (assoc :viewer-libraries (d/index-by :id libraries))
-            (update :viewer-data assoc
-                    :project project
-                    :objects objects
-                    :users (d/index-by :id users)
-                    :file file
-                    :page page
-                    :frames frames
-                    :token token
-                    :share-token share-token))))))
+            (assoc :share-links share-links)
+            (assoc :viewer {:libraries (d/index-by :id libraries)
+                            :users (d/index-by :id users)
+                            :permissions permissions
+                            :project project
+                            :pages pages
+                            :file file}))))))
 
 (defn fetch-comment-threads
   [{:keys [file-id page-id] :as params}]
@@ -168,32 +172,6 @@
         (->> (rp/query :comments {:thread-id thread-id})
              (rx/map #(partial fetched %)))))))
 
-(defn create-share-link
-  []
-  (ptk/reify ::create-share-link
-    ptk/WatchEvent
-    (watch [_ state _]
-      (let [file-id (:current-file-id state)
-            page-id (:current-page-id state)]
-        (->> (rp/mutation! :create-file-share-token {:file-id file-id
-                                                     :page-id page-id})
-             (rx/map (fn [{:keys [token]}]
-                       #(assoc-in % [:viewer-data :token] token))))))))
-
-(defn delete-share-link
-  []
-  (ptk/reify ::delete-share-link
-    ptk/WatchEvent
-    (watch [_ state _]
-      (let [file-id (:current-file-id state)
-            page-id (:current-page-id state)
-            token   (get-in state [:viewer-data :token])
-            params  {:file-id file-id
-                     :page-id page-id
-                     :token token}]
-        (->> (rp/mutation :delete-file-share-token params)
-             (rx/map (fn [_] #(update % :viewer-data dissoc :token))))))))
-
 ;; --- Zoom Management
 
 (def increase-zoom
@@ -245,29 +223,32 @@
     ptk/WatchEvent
     (watch [_ state _]
       (let [route   (:route state)
-            screen  (-> route :data :name keyword)
             qparams (:query-params route)
             pparams (:path-params route)
             index   (:index qparams)]
         (when (pos? index)
           (rx/of
            (dcm/close-thread)
-           (rt/nav screen pparams (assoc qparams :index (dec index)))))))))
+           (rt/nav :viewer pparams (assoc qparams :index (dec index)))))))))
 
 (def select-next-frame
   (ptk/reify ::select-prev-frame
     ptk/WatchEvent
     (watch [_ state _]
+      (prn "select-next-frame")
       (let [route   (:route state)
-            screen  (-> route :data :name keyword)
-            qparams (:query-params route)
             pparams (:path-params route)
+            qparams (:query-params route)
+
+            page-id (:page-id pparams)
             index   (:index qparams)
-            total   (count (get-in state [:viewer-data :frames]))]
+
+            total   (count (get-in state [:viewer :pages page-id :frames]))]
+
         (when (< index (dec total))
           (rx/of
            (dcm/close-thread)
-           (rt/nav screen pparams (assoc qparams :index (inc index)))))))))
+           (rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
 
 (s/def ::interactions-mode #{:hide :show :show-on-click})
 
@@ -329,7 +310,6 @@
         (when index
           (rx/of (go-to-frame-by-index index)))))))
 
-
 (defn go-to-section
   [section]
   (ptk/reify ::go-to-section
@@ -340,7 +320,6 @@
             qparams (:query-params route)]
         (rx/of (rt/nav :viewer pparams (assoc qparams :section section)))))))
 
-
 (defn set-current-frame [frame-id]
   (ptk/reify ::set-current-frame
     ptk/UpdateEvent
@@ -405,18 +384,50 @@
       (let [toggled? (contains? (get-in state [:viewer-local :collapsed]) id)]
         (update-in state [:viewer-local :collapsed] (if toggled? disj conj) id)))))
 
-(defn hover-shape [id hover?]
+(defn hover-shape
+  [id hover?]
   (ptk/reify ::hover-shape
     ptk/UpdateEvent
     (update [_ state]
       (assoc-in state [:viewer-local :hover] (when hover? id)))))
 
+;; --- NAV
 
 (defn go-to-dashboard
-  ([] (go-to-dashboard nil))
-  ([{:keys [team-id]}]
-   (ptk/reify ::go-to-dashboard
-     ptk/WatchEvent
+  []
+  (ptk/reify ::go-to-dashboard
+    ptk/WatchEvent
+    (watch [_ state _]
+      (let [team-id (get-in state [:viewer :project :team-id])
+            params  {:team-id team-id}]
+        (rx/of (rt/nav :dashboard-projects params))))))
+
+(defn go-to-page
+  [page-id]
+  (ptk/reify ::go-to-page
+    ptk/WatchEvent
      (watch [_ state _]
-       (let [team-id (or team-id (get-in state [:viewer-data :project :team-id]))]
-         (rx/of (rt/nav :dashboard-projects {:team-id team-id})))))))
+       (let [route   (:route state)
+             pparams (:path-params route)
+             qparams (-> (:query-params route)
+                         (assoc :index 0)
+                         (assoc :page-id page-id))
+             rname   (get-in route [:data :name])]
+         (rx/of (rt/nav rname pparams qparams))))))
+
+(defn go-to-workspace
+  [page-id]
+  (ptk/reify ::go-to-workspace
+    ptk/WatchEvent
+    (watch [_ state _]
+      (let [project-id (get-in state [:viewer :project :id])
+            file-id    (get-in state [:viewer :file :id])
+            pparams    {:project-id project-id :file-id file-id}
+            qparams    {:page-id page-id}]
+         (rx/of (rt/nav-new-window*
+                 {:rname :workspace
+                  :path-params pparams
+                  :query-params qparams
+                  :name (str "workspace-" file-id)}))))))
+
+
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 66a8aa5b3..0ea2d6805 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -37,6 +37,7 @@
    [app.main.repo :as rp]
    [app.main.streams :as ms]
    [app.main.worker :as uw]
+   [app.util.globals :as ug]
    [app.util.http :as http]
    [app.util.i18n :as i18n]
    [app.util.router :as rt]
@@ -171,7 +172,12 @@
                           (->> stream
                                (rx/filter #(= ::dwc/index-initialized %))
                                (rx/first)
-                               (rx/map #(file-initialized bundle)))))))))))
+                               (rx/map #(file-initialized bundle)))))))))
+
+    ptk/EffectEvent
+    (effect [_ _ _]
+      (let [name (str "workspace-" file-id)]
+        (unchecked-set ug/global "name" name)))))
 
 (defn- file-initialized
   [{:keys [file users project libraries] :as bundle}]
@@ -1273,10 +1279,14 @@
      ptk/WatchEvent
      (watch [_ state _]
        (let [{:keys [current-file-id current-page-id]} state
-             params {:file-id (or file-id current-file-id)
-                     :page-id (or page-id current-page-id)}]
+             pparams {:file-id (or file-id current-file-id)}
+             qparams {:page-id (or page-id current-page-id)
+                      :index 0}]
          (rx/of ::dwp/force-persist
-                (rt/nav-new-window :viewer params {:index 0})))))))
+                (rt/nav-new-window* {:rname :viewer
+                                     :path-params pparams
+                                     :query-params qparams
+                                     :name (str "viewer-" (:file-id pparams))})))))))
 
 (defn go-to-dashboard
   ([] (go-to-dashboard nil))
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index 12ab7e553..4dcbac803 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -38,6 +38,9 @@
 (def threads-ref
   (l/derived :comment-threads st/state))
 
+(def share-links
+  (l/derived :share-links st/state))
+
 ;; ---- Dashboard refs
 
 (def dashboard-local
@@ -287,8 +290,17 @@
 
 ;; ---- Viewer refs
 
+(def viewer-file
+  (l/derived :viewer-file st/state))
+
+(def viewer-project
+  (l/derived :viewer-file st/state))
+
 (def viewer-data
-  (l/derived :viewer-data st/state))
+  (l/derived :viewer st/state))
+
+(def viewer-state
+  (l/derived :viewer st/state))
 
 (def viewer-local
   (l/derived :viewer-local st/state))
diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs
index aeafb377f..bfe2d5b25 100644
--- a/frontend/src/app/main/ui.cljs
+++ b/frontend/src/app/main/ui.cljs
@@ -20,7 +20,6 @@
    [app.main.ui.context :as ctx]
    [app.main.ui.cursors :as c]
    [app.main.ui.dashboard :refer [dashboard]]
-   [app.main.ui.handoff :refer [handoff]]
    [app.main.ui.icons :as i]
    [app.main.ui.messages :as msgs]
    [app.main.ui.onboarding]
@@ -41,16 +40,17 @@
 
 (s/def ::page-id ::us/uuid)
 (s/def ::file-id ::us/uuid)
-(s/def ::viewer-path-params
-  (s/keys :req-un [::file-id ::page-id]))
-
 (s/def ::section ::us/keyword)
 (s/def ::index ::us/integer)
-(s/def ::token (s/nilable ::us/string))
+(s/def ::token (s/nilable ::us/not-empty-string))
+(s/def ::share-id ::us/uuid)
+
+(s/def ::viewer-path-params
+  (s/keys :req-un [::file-id]))
 
 (s/def ::viewer-query-params
   (s/keys :req-un [::index]
-          :opt-un [::token ::section]))
+          :opt-un [::share-id ::section ::page-id]))
 
 (def routes
   [["/auth"
@@ -71,7 +71,7 @@
     ["/feedback" :settings-feedback]
     ["/options"  :settings-options]]
 
-   ["/view/:file-id/:page-id"
+   ["/view/:file-id"
     {:name :viewer
      :conform
      {:path-params ::viewer-path-params
@@ -147,22 +147,15 @@
         [:& dashboard {:route route}]]
 
        :viewer
-       (let [index   (get-in route [:query-params :index])
-             token   (get-in route [:query-params :token])
-             section (get-in route [:query-params :section] :interactions)
-             file-id (get-in route [:path-params :file-id])
-             page-id (get-in route [:path-params :page-id])]
+       (let [{:keys [query-params path-params]} route
+             {:keys [index share-id section page-id] :or {section :interactions}} query-params
+             {:keys [file-id]} path-params]
          [:& fs/fullscreen-wrapper {}
-          (if (= section :handoff)
-            [:& handoff {:page-id page-id
-                         :file-id file-id
-                         :index index
-                         :token token}]
-            [:& viewer-page {:page-id page-id
-                             :file-id file-id
-                             :section section
-                             :index index
-                             :token token}])])
+          [:& viewer-page {:page-id page-id
+                           :file-id file-id
+                           :section section
+                           :index index
+                           :share-id share-id}]])
 
        :render-object
        (do
diff --git a/frontend/src/app/main/ui/handoff.cljs b/frontend/src/app/main/ui/handoff.cljs
deleted file mode 100644
index bee408f97..000000000
--- a/frontend/src/app/main/ui/handoff.cljs
+++ /dev/null
@@ -1,133 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.main.ui.handoff
-  (:require
-   [app.main.data.viewer :as dv]
-   [app.main.data.viewer.shortcuts :as sc]
-   [app.main.refs :as refs]
-   [app.main.store :as st]
-   [app.main.ui.handoff.left-sidebar :refer [left-sidebar]]
-   [app.main.ui.handoff.render :refer [render-frame-svg]]
-   [app.main.ui.handoff.right-sidebar :refer [right-sidebar]]
-   [app.main.ui.hooks :as hooks]
-   [app.main.ui.viewer.header :refer [header]]
-   [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
-   [app.util.dom :as dom]
-   [app.util.i18n :as i18n :refer [t tr]]
-   [app.util.keyboard :as kbd]
-   [goog.events :as events]
-   [rumext.alpha :as mf])
-  (:import goog.events.EventType))
-
-(defn handle-select-frame [frame]
-  #(do (dom/prevent-default %)
-       (dom/stop-propagation %)
-       (st/emit! (dv/select-shape (:id frame)))))
-
-(mf/defc render-panel
-  [{:keys [data state index page-id file-id]}]
-  (let [locale  (mf/deref i18n/locale)
-        frames  (:frames data [])
-        objects (:objects data)
-        frame   (get frames index)]
-
-    (mf/use-effect
-     (mf/deps index)
-     (fn []
-       (st/emit! (dv/set-current-frame (:id frame))
-                 (dv/select-shape (:id frame)))))
-
-    [:section.viewer-preview
-     (cond
-       (empty? frames)
-       [:section.empty-state
-        [:span (t locale "viewer.empty-state")]]
-
-       (nil? frame)
-       [:section.empty-state
-        [:span (t locale "viewer.frame-not-found")]]
-
-       :else
-       [:*
-        [:& left-sidebar {:frame frame}]
-        [:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
-         [:div.handoff-svg-container
-          [:& render-frame-svg {:frame-id (:id frame)
-                                :zoom (:zoom state)
-                                :objects objects}]]]
-        [:& right-sidebar {:frame frame
-                           :page-id page-id
-                           :file-id file-id}]])]))
-
-(mf/defc handoff-content
-  [{:keys [data state index page-id file-id] :as props}]
-  (let [on-mouse-wheel
-        (mf/use-callback
-         (fn [event]
-           (when (or (kbd/ctrl? event) (kbd/meta? event))
-             (dom/prevent-default event)
-             (let [event (.getBrowserEvent ^js event)
-                   delta (+ (.-deltaY ^js event)
-                            (.-deltaX ^js event))]
-               (if (pos? delta)
-                 (st/emit! dv/decrease-zoom)
-                 (st/emit! dv/increase-zoom))))))
-
-        on-mount
-        (fn []
-          ;; bind with passive=false to allow the event to be cancelled
-          ;; https://stackoverflow.com/a/57582286/3219895
-          (let [key1 (events/listen goog/global EventType.WHEEL
-                                    on-mouse-wheel #js {"passive" false})]
-            (fn []
-              (events/unlistenByKey key1))))]
-
-    (mf/use-effect on-mount)
-    (hooks/use-shortcuts ::handoff sc/shortcuts)
-
-    [:div.handoff-layout {:class (dom/classnames :force-visible
-                                                 (:show-thumbnails state))}
-     [:& header
-      {:data data
-       :state state
-       :index index
-       :section :handoff}]
-     [:div.viewer-content
-      (when (:show-thumbnails state)
-        [:& thumbnails-panel {:index index
-                              :data data
-                              :screen :handoff}])
-      [:& render-panel {:data data
-                        :state state
-                        :index index
-                        :page-id page-id
-                        :file-id file-id}]]]))
-
-(mf/defc handoff
-  [{:keys [file-id page-id index token] :as props}]
-
-  (mf/use-effect
-   (mf/deps file-id page-id token)
-   (fn []
-     (st/emit! (dv/initialize props))))
-
-  (let [data  (mf/deref refs/viewer-data)
-        state (mf/deref refs/viewer-local)]
-
-    (mf/use-effect
-      (mf/deps (:file data))
-      #(when (:file data)
-         (dom/set-html-title (tr "title.viewer"
-                                 (get-in data [:file :name])))))
-
-    (when (and data state)
-      [:& handoff-content
-       {:file-id file-id
-        :page-id page-id
-        :index index
-        :state state
-        :data data}])))
diff --git a/frontend/src/app/main/ui/share_link.cljs b/frontend/src/app/main/ui/share_link.cljs
new file mode 100644
index 000000000..b9a84565b
--- /dev/null
+++ b/frontend/src/app/main/ui/share_link.cljs
@@ -0,0 +1,232 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.share-link
+  (:require
+   [app.common.data :as d]
+   [app.config :as cf]
+   [app.main.data.common :as dc]
+   [app.main.data.messages :as dm]
+   [app.main.data.modal :as modal]
+   [app.main.refs :as refs]
+   [app.main.store :as st]
+   [app.main.ui.icons :as i]
+   [app.util.dom :as dom]
+   [app.util.i18n :as i18n :refer [tr]]
+   [app.util.logging :as log]
+   [app.util.router :as rt]
+   [app.util.webapi :as wapi]
+   [rumext.alpha :as mf]))
+
+(log/set-level! :debug)
+
+(defn prepare-params
+  [{:keys [sections pages pages-mode]}]
+  {:pages pages
+   :flags (-> #{}
+              (into (map #(str "section-" %)) sections)
+              (into (map #(str "pages-" %)) [pages-mode]))})
+
+(mf/defc share-link-dialog
+  {::mf/register modal/components
+   ::mf/register-as :share-link}
+  [{:keys [file page]}]
+  (let [slinks  (mf/deref refs/share-links)
+        router  (mf/deref refs/router)
+        route   (mf/deref refs/route)
+
+        link    (mf/use-state nil)
+        confirm (mf/use-state false)
+
+        opts    (mf/use-state
+                 {:sections #{"viewer"}
+                  :pages-mode "current"
+                  :pages #{(:id page)}})
+
+        close
+        (fn [event]
+          (dom/prevent-default event)
+          (st/emit! (modal/hide)))
+
+        select-pages-mode
+        (fn [mode]
+          (reset! confirm false)
+          (swap! opts
+                 (fn [state]
+                   (-> state
+                       (assoc :pages-mode mode)
+                       (cond-> (= mode "current") (assoc :pages #{(:id page)}))
+                       (cond-> (= mode "all") (assoc :pages (into #{} (get-in file [:data :pages]))))))))
+
+        mark-checked-page
+        (fn [event id]
+          (let [target   (dom/get-target event)
+                checked? (.-checked ^js target)]
+            (reset! confirm false)
+            (swap! opts update :pages
+                   (fn [pages]
+                     (if checked?
+                       (conj pages id)
+                       (disj pages id))))))
+
+        create-link
+        (fn [_]
+          (let [params (prepare-params @opts)
+                params (assoc params :file-id (:id file))]
+            (st/emit! (dc/create-share-link params))))
+
+        copy-link
+        (fn [_]
+          (wapi/write-to-clipboard @link)
+          (st/emit! (dm/show {:type :info
+                              :content (tr "common.share-link.link-copied-success")
+                              :timeout 3000})))
+
+        try-delete-link
+        (fn [_]
+          (reset! confirm true))
+
+        delete-link
+        (fn [_]
+          (let [params (prepare-params @opts)
+                slink  (d/seek #(= (:flags %) (:flags params)) slinks)]
+            (reset! confirm false)
+            (st/emit! (dc/delete-share-link slink)
+                      (dm/show {:type :info
+                                :content (tr "common.share-link.link-deleted-success")
+                                :timeout 3000}))))
+        ]
+
+    (mf/use-effect
+     (mf/deps file slinks @opts)
+     (fn []
+       (let [{:keys [flags pages] :as params} (prepare-params @opts)
+             slink  (d/seek #(and (= (:flags %) flags) (= (:pages %) pages)) slinks)
+             href   (when slink
+                      (let [pparams (:path-params route)
+                            qparams (-> (:query-params route)
+                                        (assoc :share-id (:id slink))
+                                        (assoc :index "0"))
+                            href    (rt/resolve router :viewer pparams qparams)]
+                        (assoc cf/public-uri :fragment href)))]
+         (reset! link (some-> href str)))))
+
+    [:div.modal-overlay
+     [:div.modal-container.share-link-dialog
+      [:div.modal-content
+       [:div.title
+        [:h2 (tr "common.share-link.title")]
+        [:div.modal-close-button
+         {:on-click close
+          :title (tr "labels.close")}
+         i/close]]
+
+       [:div.share-link-section
+        [:label (tr "labels.link")]
+        [:div.custom-input.with-icon
+         [:input {:type "text" :value (or @link "") :read-only true}]
+         [:div.help-icon {:title (tr "labels.copy")
+                          :on-click copy-link}
+          i/copy]]
+
+        [:div.hint (tr "common.share-link.permissions-hint")]]]
+
+      [:div.modal-content
+       (let [sections (:sections @opts)]
+         [:div.access-mode
+          [:div.title (tr "common.share-link.permissions-can-access")]
+          [:div.items
+           [:div.input-checkbox.check-primary.disabled
+            [:input.check-primary.input-checkbox {:type "checkbox" :disabled true}]
+            [:label (tr "labels.workspace")]]
+
+           [:div.input-checkbox.check-primary
+            [:input {:type "checkbox"
+                     :default-checked (contains? sections "viewer")}]
+            [:label (tr "labels.viewer")
+             [:span.hint "(" (tr "labels.default") ")"]]]
+
+           ;; [:div.input-checkbox.check-primary
+           ;;  [:input.check-primary.input-checkbox {:type "checkbox"}]
+           ;;  [:label "Handsoff" ]]
+           ]])
+
+       (let [mode (:pages-mode @opts)]
+         [:*
+          [:div.view-mode
+           [:div.title (tr "common.share-link.permissions-can-view")]
+           [:div.items
+            [:div.input-radio.radio-primary
+             [:input {:type "radio"
+                      :id "view-all"
+                      :checked (= "all" mode)
+                      :name "pages-mode"
+                      :on-change #(select-pages-mode "all")}]
+             [:label {:for "view-all"} (tr "common.share-link.view-all-pages")]]
+
+            [:div.input-radio.radio-primary
+             [:input {:type "radio"
+                      :id "view-current"
+                      :name "pages-mode"
+                      :checked (= "current" mode)
+                      :on-change #(select-pages-mode "current")}]
+             [:label {:for "view-current"} (tr "common.share-link.view-current-page")]]
+
+            [:div.input-radio.radio-primary
+             [:input {:type "radio"
+                      :id "view-selected"
+                      :name "pages-mode"
+                      :checked (= "selected" mode)
+                      :on-change #(select-pages-mode "selected")}]
+             [:label {:for "view-selected"} (tr "common.share-link.view-selected-pages")]]]]
+
+          (when (= "selected" mode)
+            (let [pages   (->> (get-in file [:data :pages])
+                               (map #(get-in file [:data :pages-index %])))
+                  selected (:pages @opts)]
+              [:ul.pages-selection
+               (for [page pages]
+                 [:li.input-checkbox.check-primary {:key (str (:id page))}
+                  [:input {:type "checkbox"
+                           :id (str "page-" (:id page))
+                           :on-change #(mark-checked-page % (:id page))
+                           :checked (contains? selected (:id page))}]
+                  [:label {:for (str "page-" (:id page))} (:name page)]])]))])]
+
+      [:div.modal-footer
+       (cond
+         (true? @confirm)
+         [:div.confirm-dialog
+          [:div.description (tr "common.share-link.confirm-deletion-link-description")]
+          [:div.actions
+           [:input.btn-secondary
+            {:type "button"
+             :on-click #(reset! confirm false)
+             :value (tr "labels.cancel")}]
+           [:input.btn-warning
+            {:type "button"
+             :on-click delete-link
+             :value (tr "common.share-link.remove-link")
+             }]]]
+
+         (some? @link)
+         [:input.btn-secondary
+          {:type "button"
+           :class "primary"
+           :on-click try-delete-link
+           :value (tr "common.share-link.remove-link")}]
+
+         :else
+         [:input.btn-primary
+          {:type "button"
+           :class "primary"
+           :on-click create-link
+           :value (tr "common.share-link.get-link")}])]
+
+      ]]))
+
+
+
diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs
index 212301613..187951266 100644
--- a/frontend/src/app/main/ui/viewer.cljs
+++ b/frontend/src/app/main/ui/viewer.cljs
@@ -6,276 +6,147 @@
 
 (ns app.main.ui.viewer
   (:require
-   [app.common.data :as d]
-   [app.common.geom.matrix :as gmt]
-   [app.common.geom.point :as gpt]
-   [app.common.geom.shapes :as geom]
-   [app.common.pages :as cp]
    [app.main.data.comments :as dcm]
    [app.main.data.viewer :as dv]
    [app.main.data.viewer.shortcuts :as sc]
    [app.main.refs :as refs]
    [app.main.store :as st]
-   [app.main.ui.comments :as cmt]
    [app.main.ui.hooks :as hooks]
+   [app.main.ui.share-link]
+   [app.main.ui.viewer.comments :refer [comments-layer]]
+   [app.main.ui.viewer.handoff :as handoff]
    [app.main.ui.viewer.header :refer [header]]
-   [app.main.ui.viewer.shapes :as shapes]
+   [app.main.ui.viewer.interactions :as interactions]
    [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
    [app.util.dom :as dom]
-   [app.util.i18n :as i18n :refer [t tr]]
-   [app.util.keyboard :as kbd]
+   [app.util.i18n :as i18n :refer [tr]]
    [goog.events :as events]
-   [okulary.core :as l]
    [rumext.alpha :as mf]))
 
-(defn- frame-contains?
-  [{:keys [x y width height]} {px :x py :y}]
-  (let [x2 (+ x width)
-        y2 (+ y height)]
-    (and (<= x px x2)
-         (<= y py y2))))
+(defn- calculate-size
+  [frame zoom]
+  {:width  (* (:width frame) zoom)
+   :height (* (:height frame) zoom)
+   :vbox   (str "0 0 " (:width frame 0) " " (:height frame 0))})
 
-(def threads-ref
-  (l/derived :comment-threads st/state))
+(mf/defc viewer
+  [{:keys [params data]}]
 
-(def comments-local-ref
-  (l/derived :comments-local st/state))
+  (let [{:keys [page-id section index]} params
 
-(mf/defc comments-layer
-  [{:keys [zoom frame data] :as props}]
-  (let [profile     (mf/deref refs/profile)
+        local   (mf/deref refs/viewer-local)
 
-        modifier1   (-> (gpt/point (:x frame) (:y frame))
-                        (gpt/negate)
-                        (gmt/translate-matrix))
+        file    (:file data)
+        users   (:users data)
+        project (:project data)
+        perms   (:permissions data)
 
-        modifier2   (-> (gpt/point (:x frame) (:y frame))
-                        (gmt/translate-matrix))
+        page-id (or page-id (-> file :data :pages first))
 
-        threads-map (->> (mf/deref threads-ref)
-                         (d/mapm #(update %2 :position gpt/transform modifier1)))
+        page    (mf/use-memo
+                 (mf/deps data page-id)
+                 (fn []
+                   (get-in data [:pages page-id])))
 
-        cstate      (mf/deref refs/comments-local)
+        zoom    (:zoom local)
+        frames  (:frames page)
+        frame   (get frames index)
 
-        mframe      (geom/transform-shape frame)
-        threads     (->> (vals threads-map)
-                         (dcm/apply-filters cstate profile)
-                         (filter (fn [{:keys [position]}]
-                                   (frame-contains? mframe position))))
-
-        on-bubble-click
-        (mf/use-callback
-         (mf/deps cstate)
-         (fn [thread]
-           (if (= (:open cstate) (:id thread))
-             (st/emit! (dcm/close-thread))
-             (st/emit! (dcm/open-thread thread)))))
+        size    (mf/use-memo
+                 (mf/deps frame zoom)
+                 (fn [] (calculate-size frame zoom)))
 
         on-click
         (mf/use-callback
-         (mf/deps cstate data frame)
-         (fn [event]
-           (dom/stop-propagation event)
-           (if (some? (:open cstate))
-             (st/emit! (dcm/close-thread))
-             (let [event    (.-nativeEvent ^js event)
-                   position (-> (dom/get-offset-position event)
-                                (gpt/transform modifier2))
-                   params   {:position position
-                             :page-id (get-in data [:page :id])
-                             :file-id (get-in data [:file :id])}]
-               (st/emit! (dcm/create-draft params))))))
+         (mf/deps section)
+         (fn [_]
+           (when (= section :comments)
+             (st/emit! (dcm/close-thread)))))]
 
-        on-draft-cancel
-        (mf/use-callback
-         (mf/deps cstate)
-         (st/emitf (dcm/close-thread)))
-
-        on-draft-submit
-        (mf/use-callback
-         (mf/deps frame)
-         (fn [draft]
-           (let [params (update draft :position gpt/transform modifier2)]
-             (st/emit! (dcm/create-thread params)
-                       (dcm/close-thread)))))]
-
-    [:div.comments-section {:on-click on-click}
-     [:div.viewer-comments-container
-      [:div.threads
-       (for [item threads]
-         [:& cmt/thread-bubble {:thread item
-                                :zoom zoom
-                                :on-click on-bubble-click
-                                :open? (= (:id item) (:open cstate))
-                                :key (:seqn item)}])
-
-       (when-let [id (:open cstate)]
-         (when-let [thread (get threads-map id)]
-           [:& cmt/thread-comments {:thread thread
-                                    :users (:users data)
-                                    :zoom zoom}]))
-
-       (when-let [draft (:draft cstate)]
-         [:& cmt/draft-thread {:draft (update draft :position gpt/transform modifier1)
-                               :on-cancel on-draft-cancel
-                               :on-submit on-draft-submit
-                               :zoom zoom}])]]]))
-
-
-
-(mf/defc viewport
-  {::mf/wrap [mf/memo]}
-  [{:keys [state data index section] :as props}]
-  (let [zoom          (:zoom state)
-        objects       (:objects data)
-
-        frame         (get-in data [:frames index])
-        frame-id      (:id frame)
-
-        modifier      (-> (gpt/point (:x frame) (:y frame))
-                          (gpt/negate)
-                          (gmt/translate-matrix))
-
-        update-fn     #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)
-
-        objects       (->> (d/concat [frame-id] (cp/get-children frame-id objects))
-                           (reduce update-fn objects))
-
-        interactions? (:interactions-show? state)
-        wrapper       (mf/use-memo (mf/deps objects) #(shapes/frame-container-factory objects interactions?))
-
-        ;; Retrieve frame again with correct modifier
-        frame         (get objects frame-id)
-
-        width         (* (:width frame) zoom)
-        height        (* (:height frame) zoom)
-        vbox          (str "0 0 " (:width frame 0) " " (:height frame 0))]
-
-    [:div.viewport-container
-     {:style {:width width
-              :height height
-              :state state
-              :position "relative"}}
-
-     (when (= section :comments)
-       [:& comments-layer {:width width
-                           :height height
-                           :frame frame
-                           :data data
-                           :zoom zoom}])
-
-     [:svg {:view-box vbox
-            :width width
-            :height height
-            :version "1.1"
-            :xmlnsXlink "http://www.w3.org/1999/xlink"
-            :xmlns "http://www.w3.org/2000/svg"}
-      [:& wrapper {:shape frame
-                   :show-interactions? interactions?
-                   :view-box vbox}]]]))
-
-(mf/defc main-panel
-  [{:keys [data state index section]}]
-  (let [locale  (mf/deref i18n/locale)
-        frames  (:frames data)
-        frame   (get frames index)]
-    [:section.viewer-preview
-     (cond
-       (empty? frames)
-       [:section.empty-state
-        [:span (t locale "viewer.empty-state")]]
-
-       (nil? frame)
-       [:section.empty-state
-        [:span (t locale "viewer.frame-not-found")]]
-
-       (some? state)
-       [:& viewport
-        {:data data
-         :section section
-         :index index
-         :state state
-         }])]))
-
-(mf/defc viewer-content
-  [{:keys [data state index section] :as props}]
-  (let [on-click
-        (fn [event]
-          (dom/stop-propagation event)
-          (st/emit! (dcm/close-thread))
-          (let [mode (get state :interactions-mode)]
-            (when (= mode :show-on-click)
-              (st/emit! dv/flash-interactions))))
-
-        on-mouse-wheel
-        (fn [event]
-          (when (or (kbd/ctrl? event) (kbd/meta? event))
-            (dom/prevent-default event)
-            (let [event (.getBrowserEvent ^js event)
-                  delta (+ (.-deltaY ^js event) (.-deltaX ^js event))]
-              (if (pos? delta)
-                (st/emit! dv/decrease-zoom)
-                (st/emit! dv/increase-zoom)))))
-
-        on-key-down
-        (fn [event]
-          (when (kbd/esc? event)
-            (st/emit! (dcm/close-thread))))
-
-        on-mount
-        (fn []
-          ;; bind with passive=false to allow the event to be cancelled
-          ;; https://stackoverflow.com/a/57582286/3219895
-          (let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false})
-                key2 (events/listen js/window "keydown" on-key-down)
-                key3 (events/listen js/window "click" on-click)]
-            (fn []
-              (events/unlistenByKey key1)
-              (events/unlistenByKey key2)
-              (events/unlistenByKey key3))))]
-
-    (mf/use-effect on-mount)
     (hooks/use-shortcuts ::viewer sc/shortcuts)
 
-    [:div.viewer-layout {:class (dom/classnames :force-visible
-                                                (:show-thumbnails state))}
-     [:& header
-      {:data data
-       :state state
-       :section section
-       :index index}]
+    ;; Set the page title
+    (mf/use-effect
+     (mf/deps (:name file))
+     (fn []
+       (let [name (:name file)]
+         (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name))))))
 
-     [:div.viewer-content {:on-click on-click}
-      (when (:show-thumbnails state)
-        [:& thumbnails-panel {:screen :viewer
-                              :index index
-                              :data data}])
-      [:& main-panel {:data data
-                      :section section
-                      :state state
-                      :index index}]]]))
+    (mf/use-effect
+     (fn []
+       (let [key1 (events/listen js/window "click" on-click)]
+         (fn []
+           (events/unlistenByKey key1)))))
 
+    [:div {:class (dom/classnames
+                   :force-visible (:show-thumbnails local)
+                   :viewer-layout (not= section :handoff)
+                   :handoff-layout (= section :handoff))}
+
+     [:& header {:project project
+                 :file file
+                 :page page
+                 :frame frame
+                 :permissions perms
+                 :zoom (:zoom local)
+                 :section section}]
+
+     [:div.viewer-content
+      [:& thumbnails-panel {:frames frames
+                            :show? (:show-thumbnails local false)
+                            :page page
+                            :index index}]
+      [:section.viewer-preview
+       (cond
+         (empty? frames)
+         [:section.empty-state
+          [:span (tr "viewer.empty-state")]]
+
+         (nil? frame)
+         [:section.empty-state
+          [:span (tr "viewer.frame-not-found")]]
+
+         (some? frame)
+         (if (= :handoff section)
+           [:& handoff/viewport
+            {:frame frame
+             :page page
+             :file file
+             :section section
+             :local local}]
+
+
+           [:div.viewport-container
+            {:style {:width (:width size)
+                     :height (:height size)
+                     :position "relative"}}
+
+            (when (= section :comments)
+              [:& comments-layer {:file file
+                                  :users users
+                                  :frame frame
+                                  :page page
+                                  :zoom zoom}])
+
+            [:& interactions/viewport
+             {:frame frame
+              :size size
+              :page page
+              :file file
+              :users users
+              :local local}]]))]]]))
 
 ;; --- Component: Viewer Page
 
 (mf/defc viewer-page
-  [{:keys [file-id page-id index token section] :as props}]
-  (let [data  (mf/deref refs/viewer-data)
-        state (mf/deref refs/viewer-local)]
-
-    (mf/use-effect
-     (mf/deps file-id page-id token)
+  [{:keys [file-id] :as props}]
+  (mf/use-effect
+   (mf/deps file-id)
+   (fn []
+     (st/emit! (dv/initialize props))
      (fn []
-       (st/emit! (dv/initialize props))))
+       (st/emit! (dv/finalize props)))))
 
-    (mf/use-effect
-      (mf/deps (:file data))
-      #(when-let [name (get-in data [:file :name])]
-         (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name)))))
-
-    (when (and data state)
-      [:& viewer-content
-       {:index index
-        :section section
-        :state state
-        :data data}])))
+  (when-let [data (mf/deref refs/viewer-data)]
+    (let [key (str (get-in data [:file :id]))]
+      [:& viewer {:params props :data data :key key}])))
diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs
new file mode 100644
index 000000000..bce399c69
--- /dev/null
+++ b/frontend/src/app/main/ui/viewer/comments.cljs
@@ -0,0 +1,158 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.viewer.comments
+  (:require
+   [app.common.data :as d]
+   [app.common.geom.matrix :as gmt]
+   [app.common.geom.point :as gpt]
+   [app.common.geom.shapes :as geom]
+   [app.main.data.comments :as dcm]
+   [app.main.refs :as refs]
+   [app.main.store :as st]
+   [app.main.ui.comments :as cmt]
+   [app.main.ui.components.dropdown :refer [dropdown]]
+   [app.main.ui.icons :as i]
+   [app.util.dom :as dom]
+   [app.util.i18n :as i18n :refer [tr]]
+   [okulary.core :as l]
+   [rumext.alpha :as mf]))
+
+
+(mf/defc comments-menu
+  []
+  (let [{cmode :mode cshow :show} (mf/deref refs/comments-local)
+
+        show-dropdown?  (mf/use-state false)
+        toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
+        hide-dropdown   (mf/use-fn #(reset! show-dropdown? false))
+
+        update-mode
+        (mf/use-callback
+         (fn [mode]
+           (st/emit! (dcm/update-filters {:mode mode}))))
+
+        update-show
+        (mf/use-callback
+         (fn [mode]
+           (st/emit! (dcm/update-filters {:show mode}))))]
+
+    [:div.view-options {:on-click toggle-dropdown}
+     [:span.label (tr "labels.comments")]
+     [:span.icon i/arrow-down]
+     [:& dropdown {:show @show-dropdown?
+                   :on-close hide-dropdown}
+      [:ul.dropdown.with-check
+       [:li {:class (dom/classnames :selected (= :all cmode))
+             :on-click #(update-mode :all)}
+        [:span.icon i/tick]
+        [:span.label (tr "labels.show-all-comments")]]
+
+       [:li {:class (dom/classnames :selected (= :yours cmode))
+             :on-click #(update-mode :yours)}
+        [:span.icon i/tick]
+        [:span.label (tr "labels.show-your-comments")]]
+
+       [:hr]
+
+       [:li {:class (dom/classnames :selected (= :pending cshow))
+             :on-click #(update-show (if (= :pending cshow) :all :pending))}
+        [:span.icon i/tick]
+        [:span.label (tr "labels.hide-resolved-comments")]]]]]))
+
+
+(defn- frame-contains?
+  [{:keys [x y width height]} {px :x py :y}]
+  (let [x2 (+ x width)
+        y2 (+ y height)]
+    (and (<= x px x2)
+         (<= y py y2))))
+
+(def threads-ref
+  (l/derived :comment-threads st/state))
+
+(def comments-local-ref
+  (l/derived :comments-local st/state))
+
+(mf/defc comments-layer
+  [{:keys [zoom file users frame page] :as props}]
+  (let [profile     (mf/deref refs/profile)
+
+        modifier1   (-> (gpt/point (:x frame) (:y frame))
+                        (gpt/negate)
+                        (gmt/translate-matrix))
+
+        modifier2   (-> (gpt/point (:x frame) (:y frame))
+                        (gmt/translate-matrix))
+
+        threads-map (->> (mf/deref threads-ref)
+                         (d/mapm #(update %2 :position gpt/transform modifier1)))
+
+        cstate      (mf/deref refs/comments-local)
+
+        mframe      (geom/transform-shape frame)
+        threads     (->> (vals threads-map)
+                         (dcm/apply-filters cstate profile)
+                         (filter (fn [{:keys [position]}]
+                                   (frame-contains? mframe position))))
+
+        on-bubble-click
+        (mf/use-callback
+         (mf/deps cstate)
+         (fn [thread]
+           (if (= (:open cstate) (:id thread))
+             (st/emit! (dcm/close-thread))
+             (st/emit! (dcm/open-thread thread)))))
+
+        on-click
+        (mf/use-callback
+         (mf/deps cstate frame page file)
+         (fn [event]
+           (dom/stop-propagation event)
+           (if (some? (:open cstate))
+             (st/emit! (dcm/close-thread))
+             (let [event    (.-nativeEvent ^js event)
+                   position (-> (dom/get-offset-position event)
+                                (gpt/transform modifier2))
+                   params   {:position position
+                             :page-id (:id page)
+                             :file-id (:id file)}]
+               (st/emit! (dcm/create-draft params))))))
+
+        on-draft-cancel
+        (mf/use-callback
+         (mf/deps cstate)
+         (st/emitf (dcm/close-thread)))
+
+        on-draft-submit
+        (mf/use-callback
+         (mf/deps frame)
+         (fn [draft]
+           (let [params (update draft :position gpt/transform modifier2)]
+             (st/emit! (dcm/create-thread params)
+                       (dcm/close-thread)))))]
+
+    [:div.comments-section {:on-click on-click}
+     [:div.viewer-comments-container
+      [:div.threads
+       (for [item threads]
+         [:& cmt/thread-bubble {:thread item
+                                :zoom zoom
+                                :on-click on-bubble-click
+                                :open? (= (:id item) (:open cstate))
+                                :key (:seqn item)}])
+
+       (when-let [id (:open cstate)]
+         (when-let [thread (get threads-map id)]
+           [:& cmt/thread-comments {:thread thread
+                                    :users users
+                                    :zoom zoom}]))
+
+       (when-let [draft (:draft cstate)]
+         [:& cmt/draft-thread {:draft (update draft :position gpt/transform modifier1)
+                               :on-cancel on-draft-cancel
+                               :on-submit on-draft-submit
+                               :zoom zoom}])]]]))
diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs
new file mode 100644
index 000000000..1fa925678
--- /dev/null
+++ b/frontend/src/app/main/ui/viewer/handoff.cljs
@@ -0,0 +1,68 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.viewer.handoff
+  (:require
+   [app.main.data.viewer :as dv]
+   [app.main.store :as st]
+   [app.main.ui.viewer.handoff.left-sidebar :refer [left-sidebar]]
+   [app.main.ui.viewer.handoff.render :refer [render-frame-svg]]
+   [app.main.ui.viewer.handoff.right-sidebar :refer [right-sidebar]]
+   [app.util.dom :as dom]
+   [app.util.keyboard :as kbd]
+   [goog.events :as events]
+   [rumext.alpha :as mf])
+  (:import goog.events.EventType))
+
+(defn handle-select-frame
+  [frame]
+  (fn [event]
+    (dom/prevent-default event)
+    (dom/stop-propagation event)
+    (st/emit! (dv/select-shape (:id frame)))))
+
+(mf/defc viewport
+  [{:keys [local file page frame]}]
+  (let [on-mouse-wheel
+        (fn [event]
+          (when (or (kbd/ctrl? event) (kbd/meta? event))
+            (dom/prevent-default event)
+            (let [event (.getBrowserEvent ^js event)
+                  delta (+ (.-deltaY ^js event)
+                           (.-deltaX ^js event))]
+              (if (pos? delta)
+                (st/emit! dv/decrease-zoom)
+                (st/emit! dv/increase-zoom)))))
+
+        on-mount
+        (fn []
+          ;; bind with passive=false to allow the event to be cancelled
+          ;; https://stackoverflow.com/a/57582286/3219895
+          (let [key1 (events/listen goog/global EventType.WHEEL
+                                    on-mouse-wheel #js {"passive" false})]
+            (fn []
+              (events/unlistenByKey key1))))]
+
+    (mf/use-effect on-mount)
+
+    (mf/use-effect
+     (mf/deps (:id frame))
+     (fn []
+       (st/emit! (dv/set-current-frame (:id frame))
+                 (dv/select-shape (:id frame)))))
+
+    [:*
+     [:& left-sidebar {:frame frame
+                       :local local
+                       :page page}]
+     [:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
+      [:div.handoff-svg-container
+       [:& render-frame-svg {:frame frame :page page :local local}]]]
+
+     [:& right-sidebar {:frame frame
+                        :selected (:selected local)
+                        :page page
+                        :file file}]]))
diff --git a/frontend/src/app/main/ui/handoff/attributes.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs
similarity index 70%
rename from frontend/src/app/main/ui/handoff/attributes.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes.cljs
index cdce9e76d..2792ae1fe 100644
--- a/frontend/src/app/main/ui/handoff/attributes.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs
@@ -4,18 +4,18 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes
+(ns app.main.ui.viewer.handoff.attributes
   (:require
    [app.common.geom.shapes :as gsh]
-   [app.main.ui.handoff.attributes.blur :refer [blur-panel]]
-   [app.main.ui.handoff.attributes.fill :refer [fill-panel]]
-   [app.main.ui.handoff.attributes.image :refer [image-panel]]
-   [app.main.ui.handoff.attributes.layout :refer [layout-panel]]
-   [app.main.ui.handoff.attributes.shadow :refer [shadow-panel]]
-   [app.main.ui.handoff.attributes.stroke :refer [stroke-panel]]
-   [app.main.ui.handoff.attributes.svg :refer [svg-panel]]
-   [app.main.ui.handoff.attributes.text :refer [text-panel]]
-   [app.main.ui.handoff.exports :refer [exports]]
+   [app.main.ui.viewer.handoff.attributes.blur :refer [blur-panel]]
+   [app.main.ui.viewer.handoff.attributes.fill :refer [fill-panel]]
+   [app.main.ui.viewer.handoff.attributes.image :refer [image-panel]]
+   [app.main.ui.viewer.handoff.attributes.layout :refer [layout-panel]]
+   [app.main.ui.viewer.handoff.attributes.shadow :refer [shadow-panel]]
+   [app.main.ui.viewer.handoff.attributes.stroke :refer [stroke-panel]]
+   [app.main.ui.viewer.handoff.attributes.svg :refer [svg-panel]]
+   [app.main.ui.viewer.handoff.attributes.text :refer [text-panel]]
+   [app.main.ui.viewer.handoff.exports :refer [exports]]
    [app.util.i18n :as i18n]
    [rumext.alpha :as mf]))
 
diff --git a/frontend/src/app/main/ui/handoff/attributes/blur.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs
similarity index 96%
rename from frontend/src/app/main/ui/handoff/attributes/blur.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs
index 67f3b9194..fc9f382ea 100644
--- a/frontend/src/app/main/ui/handoff/attributes/blur.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/blur.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.blur
+(ns app.main.ui.viewer.handoff.attributes.blur
   (:require
    [app.main.ui.components.copy-button :refer [copy-button]]
    [app.util.code-gen :as cg]
diff --git a/frontend/src/app/main/ui/handoff/attributes/common.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs
similarity index 98%
rename from frontend/src/app/main/ui/handoff/attributes/common.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs
index 2171979a5..c13617692 100644
--- a/frontend/src/app/main/ui/handoff/attributes/common.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/common.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.common
+(ns app.main.ui.viewer.handoff.attributes.common
   (:require
    [app.common.math :as mth]
    [app.main.store :as st]
diff --git a/frontend/src/app/main/ui/handoff/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs
similarity index 93%
rename from frontend/src/app/main/ui/handoff/attributes/fill.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs
index a2b6301dc..c1ee2269f 100644
--- a/frontend/src/app/main/ui/handoff/attributes/fill.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/fill.cljs
@@ -4,10 +4,10 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.fill
+(ns app.main.ui.viewer.handoff.attributes.fill
   (:require
    [app.main.ui.components.copy-button :refer [copy-button]]
-   [app.main.ui.handoff.attributes.common :refer [color-row]]
+   [app.main.ui.viewer.handoff.attributes.common :refer [color-row]]
    [app.util.code-gen :as cg]
    [app.util.color :as uc]
    [app.util.i18n :refer [tr]]
diff --git a/frontend/src/app/main/ui/handoff/attributes/image.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs
similarity index 97%
rename from frontend/src/app/main/ui/handoff/attributes/image.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs
index e79818868..00691e236 100644
--- a/frontend/src/app/main/ui/handoff/attributes/image.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/image.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.image
+(ns app.main.ui.viewer.handoff.attributes.image
   (:require
    [app.config :as cfg]
    [app.main.ui.components.copy-button :refer [copy-button]]
diff --git a/frontend/src/app/main/ui/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs
similarity index 98%
rename from frontend/src/app/main/ui/handoff/attributes/layout.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs
index 4704bbb0a..ea7ca7b52 100644
--- a/frontend/src/app/main/ui/handoff/attributes/layout.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.layout
+(ns app.main.ui.viewer.handoff.attributes.layout
   (:require
    [app.common.math :as mth]
    [app.main.ui.components.copy-button :refer [copy-button]]
diff --git a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs
similarity index 95%
rename from frontend/src/app/main/ui/handoff/attributes/shadow.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs
index 892f46679..67a934f5c 100644
--- a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/shadow.cljs
@@ -4,11 +4,11 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.shadow
+(ns app.main.ui.viewer.handoff.attributes.shadow
   (:require
    [app.common.data :as d]
    [app.main.ui.components.copy-button :refer [copy-button]]
-   [app.main.ui.handoff.attributes.common :refer [color-row]]
+   [app.main.ui.viewer.handoff.attributes.common :refer [color-row]]
    [app.util.code-gen :as cg]
    [app.util.i18n :refer [tr]]
    [cuerdas.core :as str]
diff --git a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs
similarity index 96%
rename from frontend/src/app/main/ui/handoff/attributes/stroke.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs
index ddd5a3263..a41d11ad5 100644
--- a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs
@@ -4,12 +4,12 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.stroke
+(ns app.main.ui.viewer.handoff.attributes.stroke
   (:require
    [app.common.data :as d]
    [app.common.math :as mth]
    [app.main.ui.components.copy-button :refer [copy-button]]
-   [app.main.ui.handoff.attributes.common :refer [color-row]]
+   [app.main.ui.viewer.handoff.attributes.common :refer [color-row]]
    [app.util.code-gen :as cg]
    [app.util.color :as uc]
    [app.util.i18n :refer [t]]
diff --git a/frontend/src/app/main/ui/handoff/attributes/svg.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs
similarity index 97%
rename from frontend/src/app/main/ui/handoff/attributes/svg.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs
index a2308c598..8e126e014 100644
--- a/frontend/src/app/main/ui/handoff/attributes/svg.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/svg.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.svg
+(ns app.main.ui.viewer.handoff.attributes.svg
   (:require
    #_[app.common.math :as mth]
    #_[app.main.ui.icons :as i]
diff --git a/frontend/src/app/main/ui/handoff/attributes/text.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
similarity index 98%
rename from frontend/src/app/main/ui/handoff/attributes/text.cljs
rename to frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
index 694c3525b..9ed9c189d 100644
--- a/frontend/src/app/main/ui/handoff/attributes/text.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
@@ -4,13 +4,13 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.attributes.text
+(ns app.main.ui.viewer.handoff.attributes.text
   (:require
    [app.common.text :as txt]
    [app.main.fonts :as fonts]
    [app.main.store :as st]
    [app.main.ui.components.copy-button :refer [copy-button]]
-   [app.main.ui.handoff.attributes.common :refer [color-row]]
+   [app.main.ui.viewer.handoff.attributes.common :refer [color-row]]
    [app.util.code-gen :as cg]
    [app.util.color :as uc]
    [app.util.i18n :refer [tr]]
diff --git a/frontend/src/app/main/ui/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs
similarity index 98%
rename from frontend/src/app/main/ui/handoff/code.cljs
rename to frontend/src/app/main/ui/viewer/handoff/code.cljs
index b6eab0744..db82e5e70 100644
--- a/frontend/src/app/main/ui/handoff/code.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.code
+(ns app.main.ui.viewer.handoff.code
   (:require
    ["js-beautify" :as beautify]
    [app.common.geom.shapes :as gsh]
diff --git a/frontend/src/app/main/ui/handoff/exports.cljs b/frontend/src/app/main/ui/viewer/handoff/exports.cljs
similarity index 99%
rename from frontend/src/app/main/ui/handoff/exports.cljs
rename to frontend/src/app/main/ui/viewer/handoff/exports.cljs
index 670a8f312..e866617f7 100644
--- a/frontend/src/app/main/ui/handoff/exports.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/exports.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.exports
+(ns app.main.ui.viewer.handoff.exports
   (:require
    [app.common.data :as d]
    [app.main.data.messages :as dm]
diff --git a/frontend/src/app/main/ui/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
similarity index 89%
rename from frontend/src/app/main/ui/handoff/left_sidebar.cljs
rename to frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
index 12d4ea031..48aa1a5aa 100644
--- a/frontend/src/app/main/ui/handoff/left_sidebar.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
@@ -4,7 +4,7 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.left-sidebar
+(ns app.main.ui.viewer.handoff.left-sidebar
   (:require
    [app.common.data :as d]
    [app.main.data.viewer :as dv]
@@ -16,12 +16,6 @@
    [okulary.core :as l]
    [rumext.alpha :as mf]))
 
-(def selected-shapes
-  (l/derived (comp :selected :viewer-local) st/state))
-
-(def page-ref
-  (l/derived (comp :page :viewer-data) st/state))
-
 (defn- make-collapsed-iref
   [id]
   #(-> (l/in [:viewer-local :collapsed id])
@@ -31,7 +25,9 @@
   [{:keys [item selected objects disable-collapse?] :as props}]
   (let [id        (:id item)
         selected? (contains? selected id)
-        item-ref (mf/use-ref nil)
+        item-ref  (mf/use-ref nil)
+
+
         collapsed-iref (mf/use-memo
                         (mf/deps id)
                         (make-collapsed-iref id))
@@ -94,10 +90,10 @@
               :objects objects
               :key (:id item)}]))])]))
 
-(mf/defc left-sidebar [{:keys [frame]}]
-  (let [page (mf/deref page-ref)
-        selected (mf/deref selected-shapes)
-        objects (:objects page)]
+(mf/defc left-sidebar
+  [{:keys [frame page local]}]
+  (let [selected (:selected local)
+        objects  (:objects page)]
 
     [:aside.settings-bar.settings-bar-left
      [:div.settings-bar-inside
diff --git a/frontend/src/app/main/ui/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs
similarity index 75%
rename from frontend/src/app/main/ui/handoff/render.cljs
rename to frontend/src/app/main/ui/viewer/handoff/render.cljs
index d4d8a89e4..093cfc846 100644
--- a/frontend/src/app/main/ui/handoff/render.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs
@@ -4,17 +4,12 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.render
+(ns app.main.ui.viewer.handoff.render
   "The main container for a frame in handoff mode"
   (:require
-   [app.common.data :as d]
-   [app.common.geom.matrix :as gmt]
-   [app.common.geom.point :as gpt]
    [app.common.geom.shapes :as geom]
-   [app.common.pages :as cp]
    [app.main.data.viewer :as dv]
    [app.main.store :as st]
-   [app.main.ui.handoff.selection-feedback :refer [selection-feedback]]
    [app.main.ui.shapes.circle :as circle]
    [app.main.ui.shapes.frame :as frame]
    [app.main.ui.shapes.group :as group]
@@ -24,17 +19,21 @@
    [app.main.ui.shapes.shape :refer [shape-container]]
    [app.main.ui.shapes.svg-raw :as svg-raw]
    [app.main.ui.shapes.text :as text]
+   [app.main.ui.viewer.handoff.selection-feedback :refer [selection-feedback]]
+   [app.main.ui.viewer.interactions :refer [prepare-objects]]
    [app.util.dom :as dom]
    [app.util.object :as obj]
    [rumext.alpha :as mf]))
 
 (declare shape-container-factory)
 
-(defn handle-hover-shape [{:keys [type id]} hover?]
-  #(when-not (#{:group :frame} type)
-     (dom/prevent-default %)
-     (dom/stop-propagation %)
-     (st/emit! (dv/hover-shape id hover?))))
+(defn handle-hover-shape
+  [{:keys [type id]} hover?]
+  (fn [event]
+    (when-not (#{:group :frame} type)
+      (dom/prevent-default event)
+      (dom/stop-propagation event)
+      (st/emit! (dv/hover-shape id hover?)))))
 
 (defn select-shape [{:keys [type id]}]
   (fn [event]
@@ -42,7 +41,7 @@
       (dom/stop-propagation event)
       (dom/prevent-default event)
       (cond
-        (.-shiftKey event)
+        (.-shiftKey ^js event)
         (st/emit! (dv/toggle-selection id))
 
         :else
@@ -154,42 +153,37 @@
               :group   [:> group-container opts]
               :svg-raw [:> svg-raw-container opts])))))))
 
-(defn adjust-frame-position [frame-id objects]
-  (let [frame        (get objects frame-id)
-        modifier     (-> (gpt/point (:x frame) (:y frame))
-                         (gpt/negate)
-                         (gmt/translate-matrix))
-
-        update-fn    #(assoc-in %1 [%2 :modifiers :displacement] modifier)
-        modifier-ids (d/concat [frame-id] (cp/get-children frame-id objects))]
-    (reduce update-fn objects modifier-ids)))
-
-(defn make-vbox [frame]
-  (str "0 0 " (:width frame 0) " " (:height frame 0)))
-
 (mf/defc render-frame-svg
-  {::mf/wrap [mf/memo]}
-  [{:keys [objects frame-id zoom] :or {zoom 1} :as props}]
+  [{:keys [page frame local]}]
+  (let [objects (mf/use-memo
+                 (mf/deps page frame)
+                 (prepare-objects page frame))
 
-  (let [objects      (adjust-frame-position frame-id objects)
-        frame        (get objects frame-id)
-        width        (* (:width frame) zoom)
-        height       (* (:height frame) zoom)
-        vbox         (make-vbox frame)
-        render-frame (mf/use-memo
-                      (mf/deps objects)
-                      #(frame-container-factory objects))]
 
-    [:svg {:id "svg-frame"
-           :view-box vbox
-           :width width
-           :height height
-           :version "1.1"
-           :xmlnsXlink "http://www.w3.org/1999/xlink"
-           :xmlns "http://www.w3.org/2000/svg"}
+        ;; Retrieve frame again with correct modifier
+        frame   (get objects (:id frame))
 
-     [:& render-frame {:shape frame
-                       :view-box vbox}]
+        zoom    (:zoom local 1)
+        width   (* (:width frame) zoom)
+        height  (* (:height frame) zoom)
+        vbox    (str "0 0 " (:width frame 0) " " (:height frame 0))
 
-     [:& selection-feedback {:frame frame}]]))
+        render  (mf/use-memo
+                 (mf/deps objects)
+                 #(frame-container-factory objects))]
+
+    [:svg
+     {:id "svg-frame"
+      :view-box vbox
+      :width width
+      :height height
+      :version "1.1"
+      :xmlnsXlink "http://www.w3.org/1999/xlink"
+      :xmlns "http://www.w3.org/2000/svg"}
+
+     [:& render {:shape frame :view-box vbox}]
+     [:& selection-feedback
+      {:frame frame
+       :objects objects
+       :local local}]]))
 
diff --git a/frontend/src/app/main/ui/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
similarity index 53%
rename from frontend/src/app/main/ui/handoff/right_sidebar.cljs
rename to frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
index 390aeaf42..2169bab30 100644
--- a/frontend/src/app/main/ui/handoff/right_sidebar.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
@@ -4,37 +4,26 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.right-sidebar
+(ns app.main.ui.viewer.handoff.right-sidebar
   (:require
    [app.common.data :as d]
-   [app.main.store :as st]
    [app.main.ui.components.tab-container :refer [tab-container tab-element]]
-   [app.main.ui.handoff.attributes :refer [attributes]]
-   [app.main.ui.handoff.code :refer [code]]
    [app.main.ui.icons :as i]
+   [app.main.ui.viewer.handoff.attributes :refer [attributes]]
+   [app.main.ui.viewer.handoff.code :refer [code]]
+   [app.main.ui.viewer.handoff.selection-feedback :refer [resolve-shapes]]
    [app.main.ui.workspace.sidebar.layers :refer [element-icon]]
-   [app.util.i18n :refer [t] :as i18n]
-   [okulary.core :as l]
+   [app.util.i18n :refer [tr]]
    [rumext.alpha :as mf]))
 
-(defn make-selected-shapes-iref
-  []
-  (let [selected->shapes
-        (fn [state]
-          (let [selected (get-in state [:viewer-local :selected])
-                objects (get-in state [:viewer-data :page :objects])
-                resolve-shape #(get objects %)]
-            (mapv resolve-shape selected)))]
-    #(l/derived selected->shapes st/state)))
-
 (mf/defc right-sidebar
-  [{:keys [frame page-id file-id]}]
-  (let [expanded (mf/use-state false)
-        locale (mf/deref i18n/locale)
-        section (mf/use-state :info #_:code)
-        selected-ref (mf/use-memo (make-selected-shapes-iref))
-        shapes (mf/deref selected-ref)
-        selected-type (-> shapes first (:type :not-found))]
+  [{:keys [frame page file selected]}]
+  (let [expanded      (mf/use-state false)
+        section       (mf/use-state :info #_:code)
+
+        shapes        (resolve-shapes (:objects page) selected)
+        selected-type (or (-> shapes first :type) :not-found)]
+
     [:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")}
      [:div.settings-bar-inside
       (when (seq shapes)
@@ -43,24 +32,24 @@
           (if (> (count shapes) 1)
             [:*
              [:span.tool-window-bar-icon i/layers]
-             [:span.tool-window-bar-title (t locale "handoff.tabs.code.selected.multiple" (count shapes))]]
+             [:span.tool-window-bar-title (tr "handoff.tabs.code.selected.multiple" (count shapes))]]
             [:*
              [:span.tool-window-bar-icon
               [:& element-icon {:shape (-> shapes first)}]]
-             [:span.tool-window-bar-title (->> selected-type d/name (str "handoff.tabs.code.selected.") (t locale))]])
+             [:span.tool-window-bar-title (->> selected-type d/name (str "handoff.tabs.code.selected.") (tr))]])
           ]
          [:div.tool-window-content
           [:& tab-container {:on-change-tab #(do
                                                (reset! expanded false)
                                                (reset! section %))
                              :selected @section}
-           [:& tab-element {:id :info :title (t locale "handoff.tabs.info")}
-            [:& attributes {:page-id page-id
-                            :file-id file-id
+           [:& tab-element {:id :info :title (tr "handoff.tabs.info")}
+            [:& attributes {:page-id (:id page)
+                            :file-id (:id file)
                             :frame frame
                             :shapes shapes}]]
 
-           [:& tab-element {:id :code :title (t locale "handoff.tabs.code")}
+           [:& tab-element {:id :code :title (tr "handoff.tabs.code")}
             [:& code {:frame frame
                       :shapes shapes
                       :on-expand #(swap! expanded not)}]]]]])]]))
diff --git a/frontend/src/app/main/ui/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
similarity index 55%
rename from frontend/src/app/main/ui/handoff/selection_feedback.cljs
rename to frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
index 70df26923..51fb68ce4 100644
--- a/frontend/src/app/main/ui/handoff/selection_feedback.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
@@ -4,12 +4,10 @@
 ;;
 ;; Copyright (c) UXBOX Labs SL
 
-(ns app.main.ui.handoff.selection-feedback
+(ns app.main.ui.viewer.handoff.selection-feedback
   (:require
    [app.common.geom.shapes :as gsh]
-   [app.main.store :as st]
    [app.main.ui.measurements :refer [selection-guides size-display measurement]]
-   [okulary.core :as l]
    [rumext.alpha :as mf]))
 
 ;; ------------------------------------------------
@@ -21,33 +19,12 @@
 (def select-guide-width 1)
 (def select-guide-dasharray 5)
 
-;; ------------------------------------------------
-;; LENSES
-;; ------------------------------------------------
-
-(defn make-selected-shapes-iref
-  "Creates a lens to the current selected shapes"
-  []
-  (let [selected->shapes
-        (fn [state]
-          (let [selected (get-in state [:viewer-local :selected])
-                objects (get-in state [:viewer-data :page :objects])
-                resolve-shape #(get objects %)]
-            (->> selected (map resolve-shape) (filterv (comp not nil?)))))]
-    #(l/derived selected->shapes st/state)))
-
-(defn make-hover-shapes-iref
-  "Creates a lens to the shapes the user is making hover"
-  []
-  (let [hover->shapes
-        (fn [state]
-          (let [hover (get-in state [:viewer-local :hover])
-                objects (get-in state [:viewer-data :page :objects])]
-            (get objects hover)))]
-    #(l/derived hover->shapes st/state)))
-
-(def selected-zoom
-  (l/derived (l/in [:viewer-local :zoom]) st/state))
+(defn resolve-shapes
+  [objects ids]
+  (let [resolve-shape #(get objects %)]
+    (into [] (comp (map resolve-shape)
+                   (filter some?))
+          ids)))
 
 ;; ------------------------------------------------
 ;; HELPERS
@@ -75,19 +52,17 @@
                      :stroke select-color
                      :stroke-width selection-rect-width}}]]))
 
-(mf/defc selection-feedback [{:keys [frame]}]
-  (let [zoom (mf/deref selected-zoom)
-
-        hover-shapes-ref (mf/use-memo (make-hover-shapes-iref))
-        hover-shape (-> (or (mf/deref hover-shapes-ref) frame)
-                        (gsh/translate-to-frame frame))
-
-        selected-shapes-ref (mf/use-memo (make-selected-shapes-iref))
-        selected-shapes (->> (mf/deref selected-shapes-ref)
+(mf/defc selection-feedback
+  [{:keys [frame local objects]}]
+  (let [{:keys [hover selected zoom]} local
+        hover-shape     (-> (or (first (resolve-shapes objects [hover])) frame)
+                            (gsh/translate-to-frame frame))
+        selected-shapes (->> (resolve-shapes objects selected)
                              (map #(gsh/translate-to-frame % frame)))
 
-        selrect (gsh/selection-rect selected-shapes)
-        bounds (frame->bounds frame)]
+        selrect         (gsh/selection-rect selected-shapes)
+        bounds          (frame->bounds frame)]
+
 
     (when (seq selected-shapes)
       [:g.selection-feedback {:pointer-events "none"}
diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs
index 061afd0f1..52377fa4c 100644
--- a/frontend/src/app/main/ui/viewer/header.cljs
+++ b/frontend/src/app/main/ui/viewer/header.cljs
@@ -6,304 +6,155 @@
 
 (ns app.main.ui.viewer.header
   (:require
-   [app.common.math :as mth]
-   [app.common.uuid :as uuid]
-   [app.config :as cfg]
-   [app.main.data.comments :as dcm]
-   [app.main.data.messages :as dm]
+   [app.main.data.modal :as modal]
    [app.main.data.viewer :as dv]
-   [app.main.data.viewer.shortcuts :as sc]
-   [app.main.refs :as refs]
    [app.main.store :as st]
    [app.main.ui.components.dropdown :refer [dropdown]]
    [app.main.ui.components.fullscreen :as fs]
    [app.main.ui.icons :as i]
+   [app.main.ui.viewer.comments :refer [comments-menu]]
+   [app.main.ui.viewer.interactions :refer [interactions-menu]]
+   [app.main.ui.workspace.header :refer [zoom-widget]]
    [app.util.dom :as dom]
    [app.util.i18n :as i18n :refer [tr]]
-   [app.util.router :as rt]
-   [app.util.webapi :as wapi]
    [rumext.alpha :as mf]))
 
-(mf/defc zoom-widget
-  {:wrap [mf/memo]}
-  [{:keys [zoom
-           on-increase
-           on-decrease
-           on-zoom-to-50
-           on-zoom-to-100
-           on-zoom-to-200
-           on-fullscreen]
-    :as props}]
-  (let [show-dropdown? (mf/use-state false)]
-    [:div.zoom-widget {:on-click #(reset! show-dropdown? true)}
-     [:span {} (str (mth/round (* 100 zoom)) "%")]
-     [:span.dropdown-button i/arrow-down]
-     [:& dropdown {:show @show-dropdown?
-                   :on-close #(reset! show-dropdown? false)}
-      [:ul.dropdown.zoom-dropdown
-       [:li {:on-click on-increase}
-        "Zoom in" [:span (sc/get-tooltip :increase-zoom)]]
-       [:li {:on-click on-decrease}
-        "Zoom out" [:span (sc/get-tooltip :decrease-zoom)]]
-       [:li {:on-click on-zoom-to-50}
-        "Zoom to 50%" [:span (sc/get-tooltip :zoom-50)]]
-       [:li {:on-click on-zoom-to-100}
-        "Zoom to 100%" [:span (sc/get-tooltip :reset-zoom)]]
-       [:li {:on-click on-zoom-to-200}
-        "Zoom to 200%" [:span (sc/get-tooltip :zoom-200)]]
-       [:li {:on-click on-fullscreen}
-        "Full screen"]]]]))
-        ;; "Full screen" [:span (sc/get-tooltip :full-screen)]]]]]))
-
-(mf/defc share-link
-  [{:keys [token] :as props}]
-  (let [show-dropdown? (mf/use-state false)
-        dropdown-ref   (mf/use-ref)
-        create (st/emitf (dv/create-share-link))
-        delete (st/emitf (dv/delete-share-link))
-
-        router (mf/deref refs/router)
-        route  (mf/deref refs/route)
-        link   (rt/resolve router
-                           :viewer
-                           (:path-params route)
-                           {:token token :index "0"})
-        link   (assoc cfg/public-uri :fragment link)
-
-        copy-link
-        (fn [_]
-          (wapi/write-to-clipboard (str link))
-          (st/emit! (dm/show {:type :info
-                              :content "Link copied successfuly!"
-                              :timeout 3000})))]
-    [:*
-     [:span.btn-primary.btn-small
-      {:alt (tr "viewer.header.share.title")
-       :on-click #(swap! show-dropdown? not)}
-      (tr "viewer.header.share.title")]
-
-     [:& dropdown {:show @show-dropdown?
-                   :on-close #(swap! show-dropdown? not)
-                   :container dropdown-ref}
-      [:div.dropdown.share-link-dropdown {:ref dropdown-ref}
-       [:span.share-link-title (tr "viewer.header.share.title")]
-       [:div.share-link-input
-        (if (string? token)
-          [:*
-            [:span.link (str link)]
-            [:span.link-button {:on-click copy-link}
-             (tr "viewer.header.share.copy-link")]]
-          [:span.link-placeholder (tr "viewer.header.share.placeholder")])]
-
-       [:span.share-link-subtitle (tr "viewer.header.share.subtitle")]
-       [:div.share-link-buttons
-        (if (string? token)
-          [:button.btn-warning {:on-click delete}
-           (tr "viewer.header.share.remove-link")]
-          [:button.btn-primary {:on-click create}
-           (tr "viewer.header.share.create-link")])]]]]))
-
-(mf/defc interactions-menu
-  [{:keys [state] :as props}]
-  (let [imode          (:interactions-mode state)
-
-        show-dropdown?  (mf/use-state false)
-        toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
-        hide-dropdown   (mf/use-fn #(reset! show-dropdown? false))
-
-        select-mode
-        (mf/use-callback
-         (fn [mode]
-           (st/emit! (dv/set-interactions-mode mode))))]
-
-    [:div.view-options
-     [:div.view-options-dropdown {:on-click toggle-dropdown}
-      [:span (tr "viewer.header.interactions")]
-      i/arrow-down]
-     [:& dropdown {:show @show-dropdown?
-                   :on-close hide-dropdown}
-      [:ul.dropdown.with-check
-       [:li {:class (dom/classnames :selected (= imode :hide))
-             :on-click #(select-mode :hide)}
-        [:span.icon i/tick]
-        [:span.label (tr "viewer.header.dont-show-interactions")]]
-
-       [:li {:class (dom/classnames :selected (= imode :show))
-             :on-click #(select-mode :show)}
-        [:span.icon i/tick]
-        [:span.label (tr "viewer.header.show-interactions")]]
-
-       [:li {:class (dom/classnames :selected (= imode :show-on-click))
-             :on-click #(select-mode :show-on-click)}
-        [:span.icon i/tick]
-        [:span.label (tr "viewer.header.show-interactions-on-click")]]]]]))
-
-(mf/defc comments-menu
-  []
-  (let [{cmode :mode cshow :show} (mf/deref refs/comments-local)
-
-        show-dropdown?  (mf/use-state false)
-        toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
-        hide-dropdown   (mf/use-fn #(reset! show-dropdown? false))
-
-        update-mode
-        (mf/use-callback
-         (fn [mode]
-           (st/emit! (dcm/update-filters {:mode mode}))))
-
-        update-show
-        (mf/use-callback
-         (fn [mode]
-           (st/emit! (dcm/update-filters {:show mode}))))]
-
-    [:div.view-options
-     [:div.icon {:on-click toggle-dropdown} i/eye]
-     [:& dropdown {:show @show-dropdown?
-                   :on-close hide-dropdown}
-      [:ul.dropdown.with-check
-       [:li {:class (dom/classnames :selected (= :all cmode))
-             :on-click #(update-mode :all)}
-        [:span.icon i/tick]
-        [:span.label (tr "labels.show-all-comments")]]
-
-       [:li {:class (dom/classnames :selected (= :yours cmode))
-             :on-click #(update-mode :yours)}
-        [:span.icon i/tick]
-        [:span.label (tr "labels.show-your-comments")]]
-
-       [:hr]
-
-       [:li {:class (dom/classnames :selected (= :pending cshow))
-             :on-click #(update-show (if (= :pending cshow) :all :pending))}
-        [:span.icon i/tick]
-        [:span.label (tr "labels.hide-resolved-comments")]]]]]))
-
-(mf/defc file-menu
-  [{:keys [project-id file-id page-id] :as props}]
-  (let [show-dropdown?  (mf/use-state false)
-        toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
-        hide-dropdown   (mf/use-fn #(reset! show-dropdown? false))
-
-        on-edit
-        (mf/use-callback
-         (mf/deps project-id file-id page-id)
-         (st/emitf (rt/nav :workspace
-                           {:project-id project-id
-                            :file-id file-id}
-                           {:page-id page-id})))]
-    [:div.file-menu
-     [:span.btn-icon-dark.btn-small {:on-click toggle-dropdown}
-      i/actions
-      [:& dropdown {:show @show-dropdown?
-                    :on-close hide-dropdown}
-       [:ul.dropdown
-        [:li {:on-click on-edit}
-         [:span.label (tr "viewer.header.edit-file")]]]]]]))
-
-(mf/defc header
-  [{:keys [data index section state] :as props}]
-  (let [{:keys [project file page frames]} data
-
-        fullscreen (mf/use-ctx fs/fullscreen-context)
-
-        total      (count frames)
-        profile    (mf/deref refs/profile)
-        teams      (mf/deref refs/teams)
-
-        team-id    (get-in data [:project :team-id])
-
-        has-permission? (and (not= uuid/zero (:id profile))
-                             (contains? teams team-id))
-
-        project-id (get-in data [:project :id])
-        file-id    (get-in data [:file :id])
-        page-id    (get-in data [:page :id])
-
-        on-click
-        (mf/use-callback
-         (st/emitf dv/toggle-thumbnails-panel))
-
-        on-goback
-        (mf/use-callback
-         (mf/deps project)
-         (st/emitf (dv/go-to-dashboard project)))
-
-        navigate
-        (mf/use-callback
-         (mf/deps file-id page-id)
-         (fn [section]
-           (st/emit! (dv/go-to-section section))))
+(mf/defc header-options
+  [{:keys [section zoom page file permissions]}]
+  (let [fullscreen (mf/use-ctx fs/fullscreen-context)
 
         toggle-fullscreen
         (mf/use-callback
-          (mf/deps fullscreen)
-          (fn []
-            (if @fullscreen (fullscreen false) (fullscreen true))))]
+         (mf/deps fullscreen)
+         (fn []
+           (if @fullscreen (fullscreen false) (fullscreen true))))
+
+        go-to-workspace
+        (mf/use-callback
+         (mf/deps page)
+         (fn []
+           (st/emit! (dv/go-to-workspace (:id page)))))
+
+        open-share-dialog
+        (mf/use-callback
+         (mf/deps page)
+         (fn []
+           (modal/show! :share-link {:page page :file file})))]
+
+    [:div.options-zone
+     (case section
+       :interactions [:& interactions-menu]
+       :comments [:& comments-menu]
+
+       [:div.view-options])
+
+     [:& zoom-widget
+      {:zoom zoom
+       :on-increase (st/emitf dv/increase-zoom)
+       :on-decrease (st/emitf dv/decrease-zoom)
+       :on-zoom-to-50 (st/emitf dv/zoom-to-50)
+       :on-zoom-to-100 (st/emitf dv/reset-zoom)
+       :on-zoom-to-200 (st/emitf dv/zoom-to-200)
+       :on-fullscreen toggle-fullscreen}]
+
+     [:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
+      {:alt (tr "viewer.header.fullscreen")
+       :on-click toggle-fullscreen}
+      (if @fullscreen
+        i/full-screen-off
+        i/full-screen)]
+
+     (when (:edit permissions)
+       [:span.btn-primary {:on-click open-share-dialog} (tr "labels.share-prototype")])
+
+     (when (:edit permissions)
+       [:span.btn-text-dark {:on-click go-to-workspace} (tr "labels.edit-file")])]))
+
+(mf/defc header-sitemap
+  [{:keys [project file page frame] :as props}]
+  (let [project-name (:name project)
+        file-name    (:name file)
+        page-name    (:name page)
+        frame-name   (:name frame)
+
+        toggle-thumbnails
+        (fn []
+          (st/emit! dv/toggle-thumbnails-panel))
+
+        show-dropdown? (mf/use-state false)
+
+        navigate-to
+        (fn [page-id]
+          (st/emit! (dv/go-to-page page-id))
+          (reset! show-dropdown? false))
+        ]
+
+     [:div.sitemap-zone {:alt (tr "viewer.header.sitemap")}
+      [:div.breadcrumb
+       {:on-click #(swap! show-dropdown? not)}
+       [:span.project-name project-name]
+       [:span "/"]
+       [:span.file-name file-name]
+       [:span "/"]
+       [:span.page-name page-name]
+       [:span.icon i/arrow-down]
+
+       [:& dropdown {:show @show-dropdown?
+                     :on-close #(swap! show-dropdown? not)}
+        [:ul.dropdown
+         (for [id (get-in file [:data :pages])]
+           [:li {:id (str id)
+                 :on-click (partial navigate-to id)}
+            (get-in file [:data :pages-index id :name])])]]]
+
+      [:div.current-frame
+       {:on-click toggle-thumbnails}
+       [:span.label "/"]
+       [:span.label frame-name]
+       [:span.icon i/arrow-down]]]))
+
+(mf/defc header
+  [{:keys [project file page frame zoom section permissions]}]
+  (let [go-to-dashboard
+        (st/emitf (dv/go-to-dashboard))
+
+        navigate
+        (fn [section]
+          (st/emit! (dv/go-to-section section)))]
+
 
     [:header.viewer-header
      [:div.main-icon
-      [:a {:on-click on-goback
+      [:a {:on-click go-to-dashboard
            ;; If the user doesn't have permission we disable the link
-           :style {:pointer-events (when-not has-permission? "none")}} i/logo-icon]]
+           :style {:pointer-events (when-not permissions "none")}} i/logo-icon]]
 
-     [:div.sitemap-zone {:alt (tr "viewer.header.sitemap")
-                         :on-click on-click}
-      [:span.project-name (:name project)]
-      [:span "/"]
-      [:span.file-name (:name file)]
-      [:span "/"]
-      [:span.page-name (:name page)]
-      [:span.show-thumbnails-button i/arrow-down]
-      [:span.counters (str (inc index) " / " total)]]
+     [:& header-sitemap {:project project :file file :page page :frame frame}]
 
      [:div.mode-zone
       [:button.mode-zone-button.tooltip.tooltip-bottom
        {:on-click #(navigate :interactions)
         :class (dom/classnames :active (= section :interactions))
-        :alt "View mode"}
+        :alt (tr "viewer.header.interactions-section")}
        i/play]
 
-      (when has-permission?
+      (when (:edit permissions)
         [:button.mode-zone-button.tooltip.tooltip-bottom
          {:on-click #(navigate :comments)
           :class (dom/classnames :active (= section :comments))
-          :alt "Comments"}
+          :alt (tr "viewer.header.comments-section")}
          i/chat])
 
-      [:button.mode-zone-button.tooltip.tooltip-bottom
-       {:on-click #(navigate :handoff)
-        :class (dom/classnames :active (= section :handoff))
-        :alt "Code mode"}
-       i/code]]
+      (when (:read permissions)
+        [:button.mode-zone-button.tooltip.tooltip-bottom
+         {:on-click #(navigate :handoff)
+          :class (dom/classnames :active (= section :handoff))
+          :alt (tr "viewer.header.handsoff-section")}
+         i/code])]
 
-     [:div.options-zone
-      (case section
-        :interactions [:& interactions-menu {:state state}]
-        :comments [:& comments-menu]
-        nil)
-
-      (when has-permission?
-        [:& share-link {:token (:token data)
-                        :page  (:page data)}])
-
-      [:& zoom-widget
-       {:zoom (:zoom state)
-        :on-increase (st/emitf dv/increase-zoom)
-        :on-decrease (st/emitf dv/decrease-zoom)
-        :on-zoom-to-50 (st/emitf dv/zoom-to-50)
-        :on-zoom-to-100 (st/emitf dv/reset-zoom)
-        :on-zoom-to-200 (st/emitf dv/zoom-to-200)
-        :on-fullscreen toggle-fullscreen}]
-
-      [:span.btn-icon-basic.btn-small.tooltip.tooltip-bottom-left
-       {:alt (tr "viewer.header.fullscreen")
-        :on-click toggle-fullscreen}
-       (if @fullscreen
-         i/full-screen-off
-         i/full-screen)]
-
-      (when has-permission?
-        [:& file-menu {:project-id project-id
-                       :file-id file-id
-                       :page-id page-id}])]]))
+     [:& header-options {:section section
+                         :permissions permissions
+                         :page page
+                         :file file
+                         :zoom zoom}]]))
 
diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs
new file mode 100644
index 000000000..d5eeb6379
--- /dev/null
+++ b/frontend/src/app/main/ui/viewer/interactions.cljs
@@ -0,0 +1,136 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.viewer.interactions
+  (:require
+   [app.common.data :as d]
+   [app.common.geom.matrix :as gmt]
+   [app.common.geom.point :as gpt]
+   [app.common.pages :as cp]
+   [app.main.data.comments :as dcm]
+   [app.main.data.viewer :as dv]
+   [app.main.refs :as refs]
+   [app.main.store :as st]
+   [app.main.ui.components.dropdown :refer [dropdown]]
+   [app.main.ui.icons :as i]
+   [app.main.ui.viewer.shapes :as shapes]
+   [app.util.dom :as dom]
+   [app.util.i18n :as i18n :refer [tr]]
+   [app.util.keyboard :as kbd]
+   [goog.events :as events]
+   [rumext.alpha :as mf]))
+
+(defn prepare-objects
+  [page frame]
+  (fn []
+    (let [objects   (:objects page)
+          frame-id  (:id frame)
+          modifier  (-> (gpt/point (:x frame) (:y frame))
+                        (gpt/negate)
+                        (gmt/translate-matrix))
+
+          update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)]
+
+      (->> (cp/get-children frame-id objects)
+           (d/concat [frame-id])
+           (reduce update-fn objects)))))
+
+
+(mf/defc viewport
+  {::mf/wrap [mf/memo]}
+  [{:keys [local page frame size]}]
+  (let [interactions? (:interactions-show? local)
+
+        objects       (mf/use-memo
+                       (mf/deps page frame)
+                       (prepare-objects page frame))
+
+        wrapper       (mf/use-memo
+                       (mf/deps objects)
+                       #(shapes/frame-container-factory objects interactions?))
+
+        ;; Retrieve frame again with correct modifier
+        frame         (get objects (:id frame))
+
+        on-click
+        (fn [_]
+          (let [mode (:interactions-mode local)]
+            (when (= mode :show-on-click)
+              (st/emit! dv/flash-interactions))))
+
+        on-mouse-wheel
+        (fn [event]
+          (when (or (kbd/ctrl? event) (kbd/meta? event))
+            (dom/prevent-default event)
+            (let [event (.getBrowserEvent ^js event)
+                  delta (+ (.-deltaY ^js event) (.-deltaX ^js event))]
+              (if (pos? delta)
+                (st/emit! dv/decrease-zoom)
+                (st/emit! dv/increase-zoom)))))
+
+        on-key-down
+        (fn [event]
+          (when (kbd/esc? event)
+            (st/emit! (dcm/close-thread))))]
+
+    (mf/use-effect
+     (fn []
+       ;; bind with passive=false to allow the event to be cancelled
+       ;; https://stackoverflow.com/a/57582286/3219895
+       (let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false})
+             key2 (events/listen js/window "keydown" on-key-down)
+             key3 (events/listen js/window "click" on-click)]
+         (fn []
+           (events/unlistenByKey key1)
+           (events/unlistenByKey key2)
+           (events/unlistenByKey key3)))))
+
+    [:svg {:view-box (:vbox size)
+           :width (:width size)
+           :height (:height size)
+           :version "1.1"
+           :xmlnsXlink "http://www.w3.org/1999/xlink"
+           :xmlns "http://www.w3.org/2000/svg"}
+     [:& wrapper {:shape frame
+                  :show-interactions? interactions?
+                  :view-box (:vbox size)}]]))
+
+
+(mf/defc interactions-menu
+  []
+  (let [local           (mf/deref refs/viewer-local)
+        mode            (:interactions-mode local)
+
+        show-dropdown?  (mf/use-state false)
+        toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
+        hide-dropdown   (mf/use-fn #(reset! show-dropdown? false))
+
+        select-mode
+        (mf/use-callback
+         (fn [mode]
+           (st/emit! (dv/set-interactions-mode mode))))]
+
+    [:div.view-options {:on-click toggle-dropdown}
+     [:span.label (tr "viewer.header.interactions")]
+     [:span.icon i/arrow-down]
+     [:& dropdown {:show @show-dropdown?
+                   :on-close hide-dropdown}
+      [:ul.dropdown.with-check
+       [:li {:class (dom/classnames :selected (= mode :hide))
+             :on-click #(select-mode :hide)}
+        [:span.icon i/tick]
+        [:span.label (tr "viewer.header.dont-show-interactions")]]
+
+       [:li {:class (dom/classnames :selected (= mode :show))
+             :on-click #(select-mode :show)}
+        [:span.icon i/tick]
+        [:span.label (tr "viewer.header.show-interactions")]]
+
+       [:li {:class (dom/classnames :selected (= mode :show-on-click))
+             :on-click #(select-mode :show-on-click)}
+        [:span.icon i/tick]
+        [:span.label (tr "viewer.header.show-interactions-on-click")]]]]]))
+
diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs
index 26397302e..52082e516 100644
--- a/frontend/src/app/main/ui/viewer/thumbnails.cljs
+++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs
@@ -10,11 +10,11 @@
    [app.main.data.viewer :as dv]
    [app.main.exports :as exports]
    [app.main.store :as st]
-   [app.main.ui.components.dropdown :refer [dropdown']]
    [app.main.ui.icons :as i]
    [app.util.dom :as dom]
    [app.util.i18n :as i18n :refer [tr]]
-   [goog.object :as gobj]
+   [app.util.object :as obj]
+   [app.util.timers :as ts]
    [rumext.alpha :as mf]))
 
 (mf/defc thumbnails-content
@@ -50,7 +50,7 @@
         on-mount
         (fn []
           (let [dom (mf/ref-val container)]
-            (reset! width (gobj/get dom "clientWidth"))))]
+            (reset! width (obj/get dom "clientWidth"))))]
 
     (mf/use-effect on-mount)
     (if expanded?
@@ -72,7 +72,8 @@
     [:span.btn-close {:on-click on-close} i/close]]])
 
 (mf/defc thumbnail-item
-  [{:keys [selected? frame on-click index objects] :as props}]
+  {::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
+  [{:keys [selected? frame on-click index objects]}]
   [:div.thumbnail-item {:on-click #(on-click % index)}
    [:div.thumbnail-preview
     {:class (dom/classnames :selected selected?)}
@@ -81,42 +82,39 @@
     [:span.name {:title (:name frame)} (:name frame)]]])
 
 (mf/defc thumbnails-panel
-  [{:keys [data index] :as props}]
+  [{:keys [frames page index show?] :as props}]
   (let [expanded? (mf/use-state false)
         container (mf/use-ref)
 
-        on-close #(st/emit! dv/toggle-thumbnails-panel)
-        selected (mf/use-var false)
+        objects   (:objects page)
 
-        on-mouse-leave
-        (fn [_]
-          (when @selected
-            (on-close)))
+        on-close  #(st/emit! dv/toggle-thumbnails-panel)
+        selected  (mf/use-var false)
 
         on-item-click
-        (fn [_ index]
-          (compare-and-set! selected false true)
-          (st/emit! (dv/go-to-frame-by-index index))
-          (when @expanded?
-            (on-close)))]
+        (mf/use-callback
+         (mf/deps @expanded?)
+         (fn [_ index]
+           (compare-and-set! selected false true)
+           (st/emit! (dv/go-to-frame-by-index index))
+           (when @expanded?
+             (on-close))))]
 
-    [:& dropdown' {:on-close on-close
-                   :container container
-                   :show true}
-     [:section.viewer-thumbnails
-      {:class (dom/classnames :expanded @expanded?)
-       :ref container
-       :on-mouse-leave on-mouse-leave}
+    [:section.viewer-thumbnails
+     {:class (dom/classnames :expanded @expanded?
+                             :invisible (not show?))
 
-      [:& thumbnails-summary {:on-toggle-expand #(swap! expanded? not)
-                              :on-close on-close
-                              :total (count (:frames data))}]
-      [:& thumbnails-content {:expanded? @expanded?
-                              :total (count (:frames data))}
-       (for [[i frame] (d/enumerate (:frames data))]
-         [:& thumbnail-item {:key i
-                             :index i
-                             :frame frame
-                             :objects (:objects data)
-                             :on-click on-item-click
-                             :selected? (= i index)}])]]]))
+      :ref container
+      }
+
+     [:& thumbnails-summary {:on-toggle-expand #(swap! expanded? not)
+                             :on-close on-close
+                             :total (count frames)}]
+     [:& thumbnails-content {:expanded? @expanded?
+                              :total (count frames)}
+      (for [[i frame] (d/enumerate frames)]
+        [:& thumbnail-item {:index i
+                            :frame frame
+                            :objects objects
+                            :on-click on-item-click
+                            :selected? (= i index)}])]]))
diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs
index e95822115..bc1856e5c 100644
--- a/frontend/src/app/main/ui/workspace/header.cljs
+++ b/frontend/src/app/main/ui/workspace/header.cljs
@@ -64,15 +64,16 @@
            on-decrease
            on-zoom-reset
            on-zoom-fit
-           on-zoom-selected]
+           on-zoom-selected
+           on-fullscreen]
     :as props}]
   (let [show-dropdown? (mf/use-state false)]
     [:div.zoom-widget {:on-click #(reset! show-dropdown? true)}
-     [:span {} (str (mth/round (* 100 zoom)) "%")]
-     [:span.dropdown-button i/arrow-down]
+     [:span.label {} (str (mth/round (* 100 zoom)) "%")]
+     [:span.icon i/arrow-down]
      [:& dropdown {:show @show-dropdown?
                    :on-close #(reset! show-dropdown? false)}
-      [:ul.zoom-dropdown
+      [:ul.dropdown
        [:li {:on-click on-increase}
         "Zoom in" [:span (sc/get-tooltip :increase-zoom)]]
        [:li {:on-click on-decrease}
@@ -82,7 +83,11 @@
        [:li {:on-click on-zoom-fit}
         "Zoom to fit all" [:span (sc/get-tooltip :fit-all)]]
        [:li {:on-click on-zoom-selected}
-        "Zoom to selected" [:span (sc/get-tooltip :zoom-selected)]]]]]))
+        "Zoom to selected" [:span (sc/get-tooltip :zoom-selected)]]
+       (when on-fullscreen
+         [:li {:on-click on-fullscreen}
+          "Full screen"])]]]))
+
 
 ;; --- Header Users
 
diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs
index dd3078c75..2329af2b6 100644
--- a/frontend/src/app/util/router.cljs
+++ b/frontend/src/app/util/router.cljs
@@ -108,14 +108,30 @@
     (let [router (:router state)
           path   (resolve router id params qparams)
           uri    (-> (u/uri cfg/public-uri)
-                     (assoc :fragment path))]
-      (js/window.open (str uri) "_blank"))))
+                     (assoc :fragment path))
+          name   (str (name id) "-" (:file-id params))]
+      (js/window.open (str uri) name))))
 
 (defn nav-new-window
   ([id] (nav-new-window id nil nil))
   ([id params] (nav-new-window id params nil))
   ([id params qparams] (NavigateNewWindow. id params qparams)))
 
+
+(defn nav-new-window*
+  [{:keys [rname path-params query-params name]}]
+  (ptk/reify ::nav-new-window
+    ptk/EffectEvent
+    (effect [_ state _]
+      (let [router (:router state)
+            path   (resolve router rname path-params query-params)
+            uri    (-> (u/uri cfg/public-uri)
+                       (assoc :fragment path))]
+
+
+
+        (js/window.open (str uri) name)))))
+
 ;; --- History API
 
 (defn initialize-history
diff --git a/frontend/translations/de.po b/frontend/translations/de.po
index 476375201..216acd571 100644
--- a/frontend/translations/de.po
+++ b/frontend/translations/de.po
@@ -1330,7 +1330,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Jeder mit dem Link hat Zugriff"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Prototyp teilen"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/el.po b/frontend/translations/el.po
index b8a58c011..5f03095a7 100644
--- a/frontend/translations/el.po
+++ b/frontend/translations/el.po
@@ -1280,7 +1280,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Όποιος έχει τον link θα έχει πρόσβαση"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Μοιραστείτε το link"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index f86d710d2..2b54ff944 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -1524,8 +1524,7 @@ msgstr "Artboard not found."
 msgid "viewer.header.dont-show-interactions"
 msgstr "Don't show interactions"
 
-#: src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.edit-file"
+msgid "labels.edit-file"
 msgstr "Edit file"
 
 #: src/app/main/ui/viewer/header.cljs
@@ -1556,8 +1555,7 @@ msgstr "Remove link"
 msgid "viewer.header.share.subtitle"
 msgstr "Anyone with the link will have access"
 
-#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Share prototype"
 
 #: src/app/main/ui/viewer/header.cljs
@@ -2762,3 +2760,57 @@ msgstr "Update"
 
 msgid "workspace.viewport.click-to-close-path"
 msgstr "Click to close the path"
+
+msgid "viewer.header.interactions-section"
+msgstr "Interactions"
+
+msgid "viewer.header.comments-section"
+msgstr "Comments"
+
+msgid "viewer.header.handsoff-section"
+msgstr "Handsoff"
+
+msgid "common.share-link.title"
+msgstr "Share prototypes"
+
+msgid "common.share-link.permissions-hint"
+msgstr "Anyone with link will have access"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "Can access"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "Can view"
+
+msgid "common.share-link.view-all-pages"
+msgstr "All pages"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "Selected pages"
+
+msgid "common.share-link.view-current-page"
+msgstr "Only this page"
+
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr "Are you sure you want to remove this link? If you do it, it's no longer be available for anyone"
+
+msgid "common.share-link.remove-link"
+msgstr "Remove link"
+
+msgid "common.share-link.get-link"
+msgstr "Get link"
+
+msgid "common.share-link.link-copied-success"
+msgstr "Link copied successfully"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "Link deleted successfully"
+
+msgid "labels.workspace"
+msgstr  "Workspace"
+
+msgid "labels.default"
+msgstr "default"
+
+msgid "labels.link"
+msgstr "Link"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 12d57eb64..0fa15cde3 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -1517,7 +1517,7 @@ msgid "viewer.header.dont-show-interactions"
 msgstr "No mostrar interacciones"
 
 #: src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.edit-file"
+msgid "labels.edit-file"
 msgstr "Editar archivo"
 
 #: src/app/main/ui/viewer/header.cljs
@@ -1549,7 +1549,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Cualquiera con el enlace podrá acceder"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Compartir prototipo"
 
 #: src/app/main/ui/viewer/header.cljs
@@ -2758,3 +2758,60 @@ msgstr "Actualizar"
 
 msgid "workspace.viewport.click-to-close-path"
 msgstr "Pulsar para cerrar la ruta"
+
+
+
+
+msgid "viewer.header.interactions-section"
+msgstr "Interacciones"
+
+msgid "viewer.header.comments-section"
+msgstr "Comentarios"
+
+msgid "viewer.header.handsoff-section"
+msgstr "Handsoff"
+
+msgid "common.share-link.title"
+msgstr "Compartir prototipos"
+
+msgid "common.share-link.permissions-hint"
+msgstr "Cualquiera con el enlace puede acceder"
+
+msgid "common.share-link.permissions-can-access"
+msgstr "Puede acceder a"
+
+msgid "common.share-link.permissions-can-view"
+msgstr "Puede ver"
+
+msgid "common.share-link.view-all-pages"
+msgstr "Todas las paginas"
+
+msgid "common.share-link.view-selected-pages"
+msgstr "Paginas seleccionadas"
+
+msgid "common.share-link.view-current-page"
+msgstr "Solo esta pagina"
+
+msgid "common.share-link.confirm-deletion-link-description"
+msgstr "¿Estas seguro que quieres eliminar el enlace? Si lo haces, el enlace dejara de funcionar para todos"
+
+msgid "common.share-link.remove-link"
+msgstr "Eliminar enlace"
+
+msgid "common.share-link.get-link"
+msgstr "Obtener enlace"
+
+msgid "common.share-link.link-copied-success"
+msgstr "Enlace copiado satisfactoriamente"
+
+msgid "common.share-link.link-deleted-success"
+msgstr "Enlace eliminado correctamente"
+
+msgid "labels.workspace"
+msgstr  "Espacio de trabajo"
+
+msgid "labels.default"
+msgstr "por defecto"
+
+msgid "labels.link"
+msgstr "Enlace"
diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po
index b43da1080..d5090089f 100644
--- a/frontend/translations/fr.po
+++ b/frontend/translations/fr.po
@@ -1226,7 +1226,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Toute personne disposant du lien aura accès"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Partager le prototype"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po
index f12369e26..221142f6a 100644
--- a/frontend/translations/ro.po
+++ b/frontend/translations/ro.po
@@ -1465,7 +1465,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Prin acest link se permite accesul public"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Distribuie link"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po
index 4d681bf2b..2229bc7b7 100644
--- a/frontend/translations/ru.po
+++ b/frontend/translations/ru.po
@@ -599,7 +599,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "Любой, у кого есть ссылка будет иметь доступ"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Поделиться ссылкой"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po
index 55e5587d0..d7488b00e 100644
--- a/frontend/translations/tr.po
+++ b/frontend/translations/tr.po
@@ -1560,7 +1560,7 @@ msgid "viewer.header.show-interactions"
 msgstr "Etkileşimleri göster"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "Bağlantıyı paylaş"
 
 #: src/app/main/ui/viewer/header.cljs
diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po
index 37b1d7005..ed9661902 100644
--- a/frontend/translations/zh_CN.po
+++ b/frontend/translations/zh_CN.po
@@ -1223,7 +1223,7 @@ msgid "viewer.header.share.subtitle"
 msgstr "任何人都可以通过本链接访问"
 
 #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs
-msgid "viewer.header.share.title"
+msgid "labels.share-prototype"
 msgstr "分享链接"
 
 #: src/app/main/ui/viewer/header.cljs