From 368e33c076085b1b73f522ac88f548a2ad081c80 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Tue, 10 Oct 2023 15:52:16 -0400
Subject: start

---
 src/client/views/nodes/DataVizBox/components/TableBox.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index dd13c5749..2fa68b00a 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -126,6 +126,7 @@ export class TableBox extends React.Component<TableBoxProps> {
             return (
                 <div
                     className="tableBox"
+                    style={{width: this.props.width+this.props.margin.right}}
                     tabIndex={0}
                     onKeyDown={e => {
                         if (this.props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
-- 
cgit v1.2.3-70-g09d2


From def79740fe8054a03772035990b4355c9aa24ebc Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Mon, 16 Oct 2023 16:06:59 -0400
Subject: center text + scrolling covers whole table height

---
 src/client/views/nodes/DataVizBox/components/Chart.scss   | 1 +
 src/client/views/nodes/DataVizBox/components/TableBox.tsx | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index c788a64c2..c8ea88d63 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -111,6 +111,7 @@
                     white-space: pre;
                     max-width: 150;
                     overflow: hidden;
+                    margin-left: 2px;
                 }
             }
         }
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index e5034daf7..3685a198e 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -82,7 +82,7 @@ export class TableBox extends React.Component<TableBoxProps> {
         return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
     }
     @computed get startID() {
-        return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0;
+        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0;
     }
     @computed get endID() {
         return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
-- 
cgit v1.2.3-70-g09d2


From 216385c7e84febce8988ef1390845b0c661fb04f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Nov 2023 12:55:59 -0500
Subject: fixed bug where tableBox's didn't render all of the rows they
 receive. lots of code cleanup -- moving things from Doc.ts to better
 locations.  Changed overlays and published docs to be local to their
 dashboard.  changed treeview icons.

---
 src/client/documents/Documents.ts                  |  46 +-
 src/client/util/CurrentUserUtils.ts                |   8 +-
 src/client/util/DocumentManager.ts                 |   5 +-
 src/client/util/LinkManager.ts                     |  32 +-
 src/client/util/SettingsManager.tsx                |  10 +-
 src/client/views/DashboardView.tsx                 |   5 +-
 src/client/views/DocumentDecorations.tsx           |  10 +-
 src/client/views/GestureOverlay.tsx                |  11 +-
 src/client/views/InkTranscription.tsx              |   9 +-
 src/client/views/InkingStroke.tsx                  |   9 +-
 src/client/views/MainView.tsx                      |   5 +-
 src/client/views/OverlayView.tsx                   | 128 +++---
 src/client/views/PropertiesView.tsx                |  12 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/collections/CollectionNoteTakingView.tsx |  10 +-
 .../views/collections/CollectionPileView.tsx       |  17 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 src/client/views/collections/CollectionSubView.tsx |   5 +-
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |  18 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   9 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  31 +-
 .../collectionLinear/CollectionLinearView.tsx      |   2 +-
 src/client/views/global/globalScripts.ts           |  25 +-
 src/client/views/nodes/ColorBox.tsx                |  19 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  14 +-
 src/client/views/nodes/EquationBox.tsx             |   3 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  11 +-
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/client/views/nodes/LinkBox.tsx                 |  11 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  30 +-
 src/client/views/nodes/LoadingBox.tsx              |  21 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   3 +-
 src/client/views/nodes/PDFBox.tsx                  |  25 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   9 +-
 src/client/views/nodes/VideoBox.tsx                |   5 +-
 src/client/views/nodes/WebBox.tsx                  |  21 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  21 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |   2 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  17 +-
 src/client/views/pdf/Annotation.tsx                |   6 +-
 src/fields/Doc.ts                                  | 464 +++++----------------
 src/fields/RichTextUtils.ts                        |   2 +-
 src/fields/util.ts                                 |   2 +-
 src/mobile/MobileInterface.tsx                     |  11 +-
 48 files changed, 482 insertions(+), 660 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c5a42aadc..edf72e45b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
@@ -1253,6 +1253,40 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
+    function matchFieldValue(doc: Doc, key: string, value: any): boolean {
+        const hasFunctionFilter = Utils.HasFunctionFilter(value);
+        if (hasFunctionFilter) {
+            return hasFunctionFilter(StrCast(doc[key]));
+        }
+        if (key === LinkedTo) {
+            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
+            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
+            const matchLink = (value: string, anchor: Doc) => {
+                const linkedToExp = value?.split('=');
+                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+            };
+            // prettier-ignore
+            return  (value === Doc.FilterNone && !allLinks.length) || 
+                    (value === Doc.FilterAny && !!allLinks.length) ||
+                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
+                                           matchLink(value,DocCast(link.link_anchor_2)) ));
+        }
+        if (typeof value === 'string') {
+            value = value.replace(`,${Utils.noRecursionHack}`, '');
+        }
+        const fieldVal = doc[key];
+        // prettier-ignore
+        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
+            (value === Doc.FilterNone && fieldVal === undefined)) {
+            return true;
+        }
+        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
+        if (vals.length) {
+            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+        }
+        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+    }
     /**
      * @param docs
      * @param childFilters
@@ -1303,8 +1337,8 @@ export namespace DocUtils {
                       const xs = Object.keys(facet).filter(value => facet[value] === 'x');
 
                       if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
-                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
-                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
+                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
+                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
                       const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
                       const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
                       const satisfiesMatchFacets = !matches.length
@@ -1802,7 +1836,7 @@ export namespace DocUtils {
                 proto.data_duration = result.duration;
             }
             if (overwriteDoc) {
-                Doc.removeCurrentlyLoading(overwriteDoc);
+                LoadingBox.removeCurrentlyLoading(overwriteDoc);
             }
             generatedDocuments.push(doc);
         }
@@ -1844,7 +1878,7 @@ export namespace DocUtils {
                 if (overwriteDoc) {
                     overwriteDoc.isLoading = false;
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
@@ -1885,7 +1919,7 @@ export namespace DocUtils {
             if ((result as any).message) {
                 if (overwriteDoc) {
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ba3c26b42..cd19daee5 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -793,11 +793,6 @@ export class CurrentUserUtils {
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
 
-    /// collection of documents rendered in the overlay layer above all tabs and other UI
-    static setupOverlays(doc: Doc, field = "myOverlayDocs") {
-        return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", isSystem: true });
-    }
-
     static setupPublished(doc:Doc, field = "myPublishedDocs") {
         return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
     }
@@ -901,7 +896,6 @@ export class CurrentUserUtils {
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
-        this.setupOverlays(doc);  // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
         this.setupPublished(doc);  // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
         this.setupTopbarButtons(doc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7cc8afaa6..f05c41fa7 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -13,6 +13,7 @@ import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { LoadingBox } from '../views/nodes/LoadingBox';
 import { PresBox } from '../views/nodes/trails';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
@@ -40,8 +41,8 @@ export class DocumentManager {
 
     //private constructor so no other class can create a nodemanager
     private constructor() {
-        if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = [];
-        observe(Doc.CurrentlyLoading, change => {
+        if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
+        observe(LoadingBox.CurrentlyLoading, change => {
             // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
             switch (change.type as any) {
                 case 'update':
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ba53a760f..608184596 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -197,14 +197,32 @@ export class LinkManager {
     }
 
     // finds the opposite anchor of a given anchor in a link
-    //TODO This should probably return undefined if there isn't an opposite anchor
-    //TODO This should also await the return value of the anchor so we don't filter out promises
     public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
-        const a1 = Cast(linkDoc.link_anchor_1, Doc, null);
-        const a2 = Cast(linkDoc.link_anchor_2, Doc, null);
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
-        if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+        const id = LinkManager.anchorIndex(linkDoc, anchor);
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        return id === '1' ? a2 : id === '2' ? a1 : id === '0' ? linkDoc : undefined;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
+        // if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+    }
+    public static anchorIndex(linkDoc: Doc, anchor: Doc) {
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        if (linkDoc.link_matchEmbeddings) {
+            return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        }
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
+        if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
+
+        // const a1 = DocCast(linkDoc.link_anchor_1);
+        // const a2 = DocCast(linkDoc.link_anchor_2);
+        // if (linkDoc.link_matchEmbeddings) {
+        //     return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        // }
+        // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
+        // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
     }
 }
 
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f75322905..e58c2687c 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -12,7 +12,9 @@ import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { GestureOverlay } from '../views/GestureOverlay';
 import { MainViewModal } from '../views/MainViewModal';
+import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
 import { undoBatch } from './UndoManager';
@@ -228,8 +230,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Show Button Labels'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())}
-                    toggleStatus={Doc.GetShowIconLabels()}
+                    onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
+                    toggleStatus={FontIconBox.ShowIconLabels}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
@@ -237,8 +239,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Recognize Ink Gestures'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())}
-                    toggleStatus={Doc.GetRecognizeGestures()}
+                    onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
+                    toggleStatus={GestureOverlay.RecognizeGestures}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 014a6358f..9e2ed7822 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -12,6 +12,7 @@ import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ImageCast, StrCast } from '../../fields/Types';
+import { normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
 import { HistoryUtil } from '../util/History';
@@ -149,7 +150,7 @@ export class DashboardView extends React.Component {
                             : this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
                                   const href = ImageCast(dashboard.thumb)?.url?.href;
                                   const shared = Object.keys(dashboard[DocAcl])
-                                      .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key))
+                                      .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
                                       .some(key => dashboard[DocAcl][key] !== AclPrivate);
                                   return (
                                       <div
@@ -372,6 +373,8 @@ export class DashboardView extends React.Component {
         Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
         dashboardDoc['pane-count'] = 1;
         freeformDoc.embedContainer = dashboardDoc;
+        dashboardDoc.myOverlayDocs = new List<Doc>();
+        dashboardDoc.myPublishedDocs = new List<Doc>();
 
         Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5a145e94a..6c55895ef 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -126,10 +126,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
                             if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.RemFromMyPublished(d.rootDoc);
                             }
                             if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.AddToMyPublished(d.rootDoc);
                             }
                         }
                         //@ts-ignore
@@ -295,8 +295,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 // open centered in a new workspace with Shift Key
                 const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
                 embedding.embedContainer = undefined;
-                embedding.x = -embedding[Width]() / 2;
-                embedding.y = -embedding[Height]() / 2;
+                embedding.x = -NumCast(embedding._width) / 2;
+                embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 35d6d73e4..caabdb09b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
-import { NumCast } from '../../fields/Types';
+import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
@@ -44,6 +44,13 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     static Instance: GestureOverlay;
     static Instances: GestureOverlay[] = [];
 
+    public static set RecognizeGestures(active) {
+        Doc.UserDoc().recognizeGestures = active;
+    }
+    public static get RecognizeGestures() {
+        return BoolCast(Doc.UserDoc().recognizeGestures);
+    }
+
     @observable public InkShape: Opt<GestureUtils.Gestures>;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
@@ -590,7 +597,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 // need to decide when to turn gestures back on
                 const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
                 let actionPerformed = false;
-                if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
+                if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
                         case GestureUtils.Gestures.Line:
                         case GestureUtils.Gestures.Triangle:
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 258bfad66..17136a737 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -2,7 +2,6 @@ import * as iink from 'iink-js';
 import { action, observable } from 'mobx';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { Cast, DateCast, NumCast } from '../../fields/Types';
 import { aggregateBounds } from '../../Utils';
@@ -287,8 +286,8 @@ export class InkTranscription extends React.Component {
             action(d => {
                 const x = NumCast(d.x);
                 const y = NumCast(d.y);
-                const width = d[Width]();
-                const height = d[Height]();
+                const width = NumCast(d._width);
+                const height = NumCast(d._height);
                 bounds.push({ x, y, width, height });
             })
         );
@@ -327,8 +326,8 @@ export class InkTranscription extends React.Component {
         // Gets a collection based on the selected nodes using a marquee view ref
         const newCollection = marqViewRef?.getCollection(selected, undefined, true);
         if (newCollection) {
-            newCollection.height = newCollection[Height]();
-            newCollection.width = newCollection[Width]();
+            newCollection.width = NumCast(newCollection._width);
+            newCollection.height = NumCast(newCollection._height);
             // if the grouping we are creating is an individual word
             if (word) {
                 newCollection.title = word;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d26c7761e..513061e79 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -24,7 +24,6 @@ import React = require('react');
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
@@ -382,10 +381,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const fillColor = this.fillColor;
 
         // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
-        if (isInkMask && (this.layoutDoc[Width]() !== Math.round(this.layoutDoc[Width]()) || this.layoutDoc[Height]() !== Math.round(this.layoutDoc[Height]()))) {
+        if (isInkMask && (this.layoutDoc._width !== Math.round(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
             setTimeout(() => {
-                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[Width]()));
-                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[Height]()));
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc._width));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
             });
         }
         const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
@@ -481,7 +480,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
                             pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
-                            width: this.layoutDoc[Width](),
+                            width: NumCast(this.layoutDoc._width),
                             transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da5e4f966..ccb46d7ec 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -261,6 +261,8 @@ export class MainView extends React.Component {
                 fa.faWindowRestore,
                 fa.faFolder,
                 fa.faFolderOpen,
+                fa.faFolderPlus,
+                fa.faFolderClosed,
                 fa.faMapPin,
                 fa.faMapMarker,
                 fa.faFingerprint,
@@ -450,6 +452,7 @@ export class MainView extends React.Component {
                 fa.faSortUp,
                 fa.faSortDown,
                 fa.faTable,
+                fa.faTableCells,
                 fa.faTableColumns,
                 fa.faTh,
                 fa.faThList,
@@ -475,11 +478,11 @@ export class MainView extends React.Component {
                 fa.faBookmark,
                 fa.faList,
                 fa.faListOl,
-                fa.faFolderPlus,
                 fa.faLightbulb,
                 fa.faBookOpen,
                 fa.faMapMarkerAlt,
                 fa.faSearchPlus,
+                fa.faSolarPanel,
                 fa.faVolumeUp,
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index c174befc0..c7b59a8e2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { NumCast } from '../../fields/Types';
@@ -119,7 +119,7 @@ export class OverlayView extends React.Component {
             new _global.ResizeObserver(
                 action((entries: any) => {
                     for (const entry of entries) {
-                        DocListCast(Doc.MyOverlayDocs?.data).forEach(doc => {
+                        Doc.MyOverlayDocs.forEach(doc => {
                             if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
                                 doc.overlayX = entry.contentRect.width - 10;
                             }
@@ -184,70 +184,68 @@ export class OverlayView extends React.Component {
     );
 
     @computed get overlayDocs() {
-        return DocListCast(Doc.MyOverlayDocs?.data)
-            .filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES)
-            .map(d => {
-                let offsetx = 0,
-                    offsety = 0;
-                const dref = React.createRef<HTMLDivElement>();
-                const onPointerMove = action((e: PointerEvent, down: number[]) => {
-                    if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
-                    if (e.buttons === 1) {
-                        d.overlayX = e.clientX + offsetx;
-                        d.overlayY = e.clientY + offsety;
-                    }
-                    if (e.metaKey) {
-                        const dragData = new DragManager.DocumentDragData([d]);
-                        dragData.offset = [-offsetx, -offsety];
-                        dragData.dropAction = 'move';
-                        dragData.removeDocument = this.removeOverlayDoc;
-                        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                            return dragData.removeDocument!(doc) ? addDocument(doc) : false;
-                        };
-                        DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
-                        return true;
-                    }
-                    return false;
-                });
-
-                const onPointerDown = (e: React.PointerEvent) => {
-                    setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
-                    offsetx = NumCast(d.overlayX) - e.clientX;
-                    offsety = NumCast(d.overlayY) - e.clientY;
-                };
-                return (
-                    <div
-                        className="overlayView-doc"
-                        ref={dref}
-                        key={d[Id]}
-                        onPointerDown={onPointerDown}
-                        style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
-                        <DocumentView
-                            Document={d}
-                            rootSelected={returnTrue}
-                            bringToFront={emptyFunction}
-                            addDocument={undefined}
-                            removeDocument={this.removeOverlayDoc}
-                            PanelWidth={d[Width]}
-                            PanelHeight={d[Height]}
-                            ScreenToLocalTransform={this.docScreenToLocalXf(d)}
-                            renderDepth={1}
-                            hideDecorations={true}
-                            isDocumentActive={returnTrue}
-                            isContentActive={returnTrue}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            focus={emptyFunction}
-                            styleProvider={DefaultStyleProvider}
-                            docViewPath={returnEmptyDoclist}
-                            addDocTab={DocumentViewInternal.addDocTabFunc}
-                            pinToPres={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                    </div>
-                );
+        return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
+            let offsetx = 0,
+                offsety = 0;
+            const dref = React.createRef<HTMLDivElement>();
+            const onPointerMove = action((e: PointerEvent, down: number[]) => {
+                if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
+                if (e.buttons === 1) {
+                    d.overlayX = e.clientX + offsetx;
+                    d.overlayY = e.clientY + offsety;
+                }
+                if (e.metaKey) {
+                    const dragData = new DragManager.DocumentDragData([d]);
+                    dragData.offset = [-offsetx, -offsety];
+                    dragData.dropAction = 'move';
+                    dragData.removeDocument = this.removeOverlayDoc;
+                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+                        return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+                    };
+                    DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+                    return true;
+                }
+                return false;
             });
+
+            const onPointerDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
+                offsetx = NumCast(d.overlayX) - e.clientX;
+                offsety = NumCast(d.overlayY) - e.clientY;
+            };
+            return (
+                <div
+                    className="overlayView-doc"
+                    ref={dref}
+                    key={d[Id]}
+                    onPointerDown={onPointerDown}
+                    style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
+                    <DocumentView
+                        Document={d}
+                        rootSelected={returnTrue}
+                        bringToFront={emptyFunction}
+                        addDocument={undefined}
+                        removeDocument={this.removeOverlayDoc}
+                        PanelWidth={d[Width]}
+                        PanelHeight={d[Height]}
+                        ScreenToLocalTransform={this.docScreenToLocalXf(d)}
+                        renderDepth={1}
+                        hideDecorations={true}
+                        isDocumentActive={returnTrue}
+                        isContentActive={returnTrue}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        styleProvider={DefaultStyleProvider}
+                        docViewPath={returnEmptyDoclist}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
+                        pinToPres={emptyFunction}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                    />
+                </div>
+            );
+        });
     }
 
     public static ShowSpinner() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d37971517..49bf982ec 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,12 +5,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@material-ui/core';
 import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
 import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
 import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -123,16 +123,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
     }
 
-    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
-    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
+    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(NumCast(this.selectedDoc?._width), this.props.width - 20));
+    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedDoc?._width) ? Math.min(NumCast(this.selectedDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
 
     @action
     docWidth = () => {
         if (this.selectedDoc) {
             const layoutDoc = this.selectedDoc;
             const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
-            if (aspect) return Math.min(layoutDoc[Width](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
-            return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[Width](), this.props.width - 20) : this.props.width - 20;
+            if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
+            return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
         }
         return 0;
     };
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 72d7cd1c5..2162d8878 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -105,7 +105,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
                 return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
             }
-            return Doc.toIcon(doc, isEmpty ? undefined : isOpen);
+            return Doc.toIcon(doc, isOpen);
         case StyleProp.TreeViewSortings:
             const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
             allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
@@ -117,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
             if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
                 const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.SELECTED).length;
-                const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
+                const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index afeef5a8f..bb6d94590 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -289,7 +289,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
         const existingWidth = existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
-        const width = d.layout_fitWidth ? maxWidth : d[Width]();
+        const width = d.layout_fitWidth ? maxWidth : NumCast(d._width);
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
     }
 
@@ -299,8 +299,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const docWid = this.getDocWidth(d);
             return Math.min(maxHeight, (docWid * nh) / nw);
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 91701b213..abb12a8ab 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,7 +1,6 @@
 import { action, computed, IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -79,12 +78,12 @@ export class CollectionPileView extends CollectionSubView() {
     toggleStarburst = action(() => {
         this.layoutDoc._freeform_scale = undefined;
         if (this.layoutEngine() === computeStarburstLayout.name) {
-            if (this.rootDoc[Width]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
-                this.rootDoc._starburstDiameter = this.rootDoc[Width]();
+            if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+                this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width);
             }
             const defaultSize = 110;
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
@@ -93,10 +92,10 @@ export class CollectionPileView extends CollectionSubView() {
             this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
-            this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
-            this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
+            this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
+            this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.layoutDoc.background;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0b29e7286..e5695f63b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,17 +4,18 @@ import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,7 +32,6 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
 import { CollectionSubView } from './CollectionSubView';
-import { SettingsManager } from '../../util/SettingsManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -378,7 +378,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
-            return Math.min(d[Width](), maxWidth);
+            return Math.min(NumCast(d._width), maxWidth);
         }
         return maxWidth;
     }
@@ -387,8 +387,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e9192ebbe..7c8b2f195 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,6 +25,7 @@ import { DocComponent } from '../DocComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
 import React = require('react');
+import { LoadingBox } from '../nodes/LoadingBox';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -450,13 +451,13 @@ export function CollectionSubView<X>(moreProps?: X) {
             if (typeof files === 'string') {
                 const loading = Docs.Create.LoadingDocument(files, options);
                 generatedDocuments.push(loading);
-                Doc.addCurrentlyLoading(loading);
+                LoadingBox.addCurrentlyLoading(loading);
                 DocUtils.uploadYoutubeVideoLoading(files, {}, loading);
             } else {
                 generatedDocuments.push(
                     ...files.map(file => {
                         const loading = Docs.Create.LoadingDocument(file, options);
-                        Doc.addCurrentlyLoading(loading);
+                        LoadingBox.addCurrentlyLoading(loading);
                         DocUtils.uploadFileToDoc(file, {}, loading);
                         return loading;
                     })
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e408c193a..761192a22 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -371,8 +371,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     marginX = () => NumCast(this.doc._xMargin);
     marginTop = () => NumCast(this.doc._yMargin);
     marginBot = () => NumCast(this.doc._yMargin);
-    documentTitleWidth = () => Math.min(this.layoutDoc?.[Width](), this.panelWidth());
-    documentTitleHeight = () => (this.layoutDoc?.[Height]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
+    documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
+    documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 41f3b2603..180578a36 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, ObservableSet, reactio
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
@@ -539,7 +539,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                             default:                  return 'gray';
                         }
                     })(doc.type as DocumentType);
-                    return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
+                    return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
             }
         }
     };
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 193c70add..dbce45fda 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,9 +1,10 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton, Size } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -19,6 +20,7 @@ import { DragManager, dropActionType } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
@@ -32,11 +34,9 @@ import { KeyValueBox } from '../nodes/KeyValueBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
+import { TreeSort } from './TreeSort';
 import './TreeView.scss';
 import React = require('react');
-import { IconButton, Size } from 'browndash-components';
-import { TreeSort } from './TreeSort';
-import { SettingsManager } from '../../util/SettingsManager';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -455,7 +455,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     embeddedPanelHeight = () => {
         const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
-            layoutDoc[Height](),
+            NumCast(layoutDoc._height),
             this.MAX_EMBED_HEIGHT,
             (() => {
                 const aspect = Doc.NativeAspect(layoutDoc);
@@ -464,7 +464,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
                         : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
-                    : (this.embeddedPanelWidth() * layoutDoc[Height]()) / layoutDoc[Width]();
+                    : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
             })()
         );
     };
@@ -741,7 +741,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             <div
                 className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
                 key="bullet"
-                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
+                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
                 onClick={this.bulletClick}
                 style={
                     this.props.treeView.outlineMode
@@ -872,7 +872,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
-                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
+                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return treeView.outlineMode ? null : (
                     <div
@@ -1265,7 +1265,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
-                    return aspect ? Math.min(childLayout[Width](), rowWidth()) / aspect : childLayout[Height]();
+                    return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height);
                 };
                 return (
                     <TreeView
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d93e44ab7..f9f9b5637 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,5 +1,4 @@
 import { Doc, Field, FieldResult } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -91,8 +90,8 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
         docMap.set(layout[Id], {
             x: NumCast(layout.x),
             y: NumCast(layout.y),
-            width: layout[Width](),
-            height: layout[Height](),
+            width: NumCast(layout._width),
+            height: NumCast(layout._height),
             pair: { layout, data },
             transition: 'all .3s',
             replica: '',
@@ -106,8 +105,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
     childPairs.forEach(({ layout, data }, i) => {
-        const aspect = layout[Height]() / layout[Width]();
-        const docSize = Math.min(Math.min(400, layout[Width]()), Math.min(400, layout[Width]()) / aspect) * burstScale;
+        const aspect = NumCast(layout._height) / NumCast(layout._width);
+        const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale;
         const deg = (i / childPairs.length) * Math.PI * 2;
         docMap.set(layout[Id], {
             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index da0f7c893..ee3dcca11 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -460,6 +460,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY));
 
+    static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+        const doc2Layout = Doc.Layout(doc2);
+        const doc1Layout = Doc.Layout(doc1);
+        const x2 = NumCast(doc2.x) - clusterDistance;
+        const y2 = NumCast(doc2.y) - clusterDistance;
+        const w2 = NumCast(doc2Layout._width) + clusterDistance;
+        const h2 = NumCast(doc2Layout._height) + clusterDistance;
+        const x = NumCast(doc1.x) - clusterDistance;
+        const y = NumCast(doc1.y) - clusterDistance;
+        const w = NumCast(doc1Layout._width) + clusterDistance;
+        const h = NumCast(doc1Layout._height) + clusterDistance;
+        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    }
     pickCluster(probe: number[]) {
         return this.childLayoutPairs
             .map(pair => pair.layout)
@@ -519,7 +532,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             docs.map(doc =>
                 this._clusterSets.map((set, i) =>
                     set.map(member => {
-                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                             docFirst.layout_cluster = i;
                         }
                     })
@@ -560,7 +573,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.layout_cluster = -1;
             this._clusterSets.forEach((set, i) =>
                 set.forEach(member => {
-                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                         doc.layout_cluster = i;
                     }
                 })
@@ -737,9 +750,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     const inks = this.getActiveDocuments().filter(doc => {
                         if (doc.type === 'ink') {
                             const l = NumCast(doc.x);
-                            const r = l + doc[Width]();
+                            const r = l + NumCast(doc._width);
                             const t = NumCast(doc.y);
-                            const b = t + doc[Height]();
+                            const b = t + NumCast(doc._height);
                             const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
                             return pass;
                         }
@@ -1217,7 +1230,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
         const layoutdoc = Doc.Layout(doc);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
-        const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[Width](), NumCast(doc.y) + layoutdoc[Height]());
+        const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height));
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
         if (scale !== undefined) {
@@ -1586,14 +1599,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._disposers.groupBounds = reaction(
                     () => {
                         if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
-                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[Width](), height: cd[Height]() }));
+                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
                         }
                         return undefined;
                     },
                     cbounds => {
                         if (cbounds) {
-                            const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[Width]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[Height]() / 2];
+                            const c = [NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) / 2, NumCast(this.layoutDoc.y) + NumCast(this.layoutDoc._height) / 2];
                             const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
@@ -1680,8 +1693,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
             this.props.docViewPath().lastElement().ContentDiv!,
-            this.layoutDoc[Width](),
-            this.layoutDoc[Height](),
+            NumCast(this.layoutDoc._width),
+            NumCast(this.layoutDoc._height),
             this.props.PanelWidth(),
             this.props.PanelHeight(),
             0,
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 9a2c79a22..4267a9059 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 894afebfd..b529991cb 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,25 +1,24 @@
 import { Colors } from 'browndash-components';
-import { runInAction, action } from 'mobx';
-import { aggregateBounds } from '../../../Utils';
+import { action, runInAction } from 'mobx';
 import { Doc } from '../../../fields/Doc';
-import { Width, Height } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { aggregateBounds } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoable, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { GestureOverlay } from '../GestureOverlay';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
 import { InkTranscription } from '../InkTranscription';
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor, InkingStroke } from '../InkingStroke';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { WebBox } from '../nodes/WebBox';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentView } from '../nodes/DocumentView';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { WebBox } from '../nodes/WebBox';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views().length <= 0;
@@ -239,8 +238,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
                 action(d => {
                     const x = NumCast(d.x);
                     const y = NumCast(d.y);
-                    const width = d[Width]();
-                    const height = d[Height]();
+                    const width = NumCast(d._width);
+                    const height = NumCast(d._height);
                     bounds.push({ x, y, width, height });
                 })
             );
@@ -277,8 +276,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
             // TODO: nda - this is the code to actually get a new grouped collection
             const newCollection = marqViewRef?.getCollection(selected, undefined, true);
             if (newCollection) {
-                newCollection.height = newCollection[Height]();
-                newCollection.width = newCollection[Width]();
+                newCollection.height = NumCast(newCollection._height);
+                newCollection.width = NumCast(newCollection._width);
             }
 
             // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 1b6fe5748..23ffd1080 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -3,10 +3,12 @@ import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Height } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { StrCast } from '../../../fields/Types';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { DashColor } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -14,8 +16,6 @@ import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth }
 import './ColorBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { RichTextMenu } from './formattedText/RichTextMenu';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { DashColor } from '../../../Utils';
 
 @observer
 export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
-        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]());
+        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.rootDoc._height), this.props.PanelWidth() / NumCast(this.rootDoc._width));
         return (
             <div
                 className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
@@ -84,9 +84,6 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 }
 
-
-ScriptingGlobals.add(
-    function interpColors(c1:string, c2:string, weight=0.5) {
-        return DashColor(c1).mix(DashColor(c2),weight)
-    }
-)
\ No newline at end of file
+ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
+    return DashColor(c1).mix(DashColor(c2), weight);
+});
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index b88389de6..147dfb182 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -219,7 +219,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter(rowId => this.startID <= rowId && rowId <= this.endID)
+                                    .filter((rowId, i) => this.startID <= i && i <= this.endID)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d87efb7b1..943408010 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocData, DocViews, Width } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -141,7 +141,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getCenter?: (xf: Transform) => { X: number; Y: number };
-    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } };
+    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
@@ -1016,7 +1016,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     removeDocument={this.hideLink(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
-                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${Doc.LinkEndpoint(link, this.rootDoc)}`)}
+                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)}
                 />
             </div>
         ));
@@ -1520,7 +1520,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
-        if (this.docView._componentView?.screenBounds) {
+        if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
         const xf = this.docView.props
@@ -1712,7 +1712,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
     const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
-    let wid = linkSource[Width]();
+    let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
     const links = LinkManager.Links(linkSource);
     links.forEach(link => {
@@ -1723,7 +1723,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
             embedding.x = wid;
             embedding.y = 0;
             embedding._lockedPosition = false;
-            wid += otherdoc[Width]();
+            wid += NumCast(otherdoc._width);
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a77e4bdd1..d8b8bcb27 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -2,7 +2,6 @@ import EquationEditor from 'equation-editor-react';
 import { action, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -68,7 +67,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (e.key === 'Tab') {
             const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
-                x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](),
+                x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width),
                 y: NumCast(this.layoutDoc.y),
                 _width: 400,
                 _height: 300,
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index d2e1293da..de5ad1631 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -46,6 +46,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
+    //
+    // This controls whether fontIconButtons will display labels under their icons or not
+    //
+    public static get ShowIconLabels() {
+        return BoolCast(Doc.UserDoc()._showLabel);
+    }
+    public static set ShowIconLabels(show: boolean) {
+        Doc.UserDoc()._showLabel = show;
+    }
     @observable noTooltip = false;
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
@@ -161,7 +170,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     Doc.UnBrushAllDocs();
                 })}>
                 {this.Icon(color)}
-                {!this.label || !Doc.GetShowIconLabels() ? null : (
+                {!this.label || !FontIconBox.ShowIconLabels ? null : (
                     <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
                         {' '}
                         {this.label}{' '}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2f4f788d4..f7dee1cc1 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { DocData, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -116,7 +116,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
         const layoutDoc = this.layoutDoc;
         this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }),
+            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
             ({ nativeSize, width }) => {
                 if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
@@ -240,7 +240,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
-            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
+            cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
         }
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index e66fed84b..a4dd5e7ef 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,7 +2,6 @@ import React = require('react');
 import { Bezier } from 'bezier-js';
 import { computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
@@ -36,8 +35,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
             const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
             const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
-            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) };
-            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) };
+            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
+            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) };
 
             const pts = [] as number[][];
             pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
@@ -51,7 +50,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             );
             return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
         }
-        return { left: 0, top: 0, right: 0, bottom: 0, center: undefined };
+        return undefined;
     };
     disposer: IReactionDisposer | undefined;
     componentDidMount() {
@@ -66,8 +65,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
                     const a_invXf = a.props.ScreenToLocalTransform().inverse();
                     const b_invXf = b.props.ScreenToLocalTransform().inverse();
-                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
-                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
+                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
+                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
                     const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
 
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 198cbe851..7e4f1da8e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,13 +3,13 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import wiki from 'wikijs';
-import { Doc, DocCastAsync, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
@@ -19,7 +19,6 @@ import { Transform } from '../../util/Transform';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
-import { DocumentManager } from '../../util/DocumentManager';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -56,21 +55,20 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         LinkDocPreview._instance = this;
     }
 
-    @action init() {
+    @action
+    init() {
         var linkTarget = this.props.linkDoc;
         this._linkSrc = this.props.linkSrc;
         this._linkDoc = this.props.linkDoc;
-        const link_anchor_1 = this._linkDoc?.link_anchor_1 as Doc;
-        const link_anchor_2 = this._linkDoc?.link_anchor_2 as Doc;
+        const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
+        const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
         if (link_anchor_1 && link_anchor_2) {
             linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
         }
         if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
-            // want to show annotation embedContainer document if annotation is not text
-            linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._markerTargetDoc = this._targetDoc = anno)));
-        } else {
-            this._markerTargetDoc = this._targetDoc = linkTarget;
+            linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
         }
+        this._markerTargetDoc = this._targetDoc = linkTarget;
         this._toolTipText = '';
         this.updateHref();
     }
@@ -190,17 +188,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
 
     width = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height]();
+        if (NumCast(this._targetDoc._width) < NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._height)) * NumCast(this._targetDoc._width)) / NumCast(this._targetDoc._height);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Width](), 225));
+        return Math.min(225, NumCast(this._targetDoc._width, 225));
     };
     height = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width]();
+        if (NumCast(this._targetDoc._width) > NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._width)) * NumCast(this._targetDoc._height)) / NumCast(this._targetDoc._width);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Height](), 225));
+        return Math.min(225, NumCast(this._targetDoc._height, 225));
     };
     @computed get previewHeader() {
         return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index bdc074e0c..4bb0f14d2 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -1,4 +1,4 @@
-import { observable, runInAction } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
@@ -36,11 +36,28 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LoadingBox, fieldKey);
     }
+    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
+    // removes from currently loading display
+    @action
+    public static removeCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading) {
+            const index = LoadingBox.CurrentlyLoading.indexOf(doc);
+            index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+        }
+    }
+
+    // adds doc to currently loading display
+    @action
+    public static addCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
+            LoadingBox.CurrentlyLoading.push(doc);
+        }
+    }
 
     _timer: any;
     @observable progress = '';
     componentDidMount() {
-        if (!Doc.CurrentlyLoading?.includes(this.rootDoc)) {
+        if (!LoadingBox.CurrentlyLoading?.includes(this.rootDoc)) {
             this.rootDoc.loadingError = 'Upload interrupted, please try again';
         } else {
             const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 08dda2e1f..37935c513 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
+import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -170,7 +170,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this.layoutDoc._layout_showSidebar = true;
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index d38857d90..c42664abe 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, runInAc
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
@@ -368,7 +367,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this._showSidebar = true;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c068d9dd7..bde1d5fa4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -63,7 +62,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         super(props);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
         if (this.pdfUrl) {
             if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
             else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
@@ -104,15 +103,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
-        newDiv.style.width = this.layoutDoc[Width]().toString();
-        newDiv.style.height = this.layoutDoc[Height]().toString();
+        newDiv.style.width = NumCast(this.layoutDoc._width).toString();
+        newDiv.style.height = NumCast(this.layoutDoc._height).toString();
         this.replaceCanvases(docViewContent, newDiv);
         const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1);
-        const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1);
+        const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1);
+        const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
@@ -169,8 +168,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             CollectionFreeFormView.UpdateIcon(
                 filename,
                 docViewContent,
-                this.layoutDoc[Width](),
-                this.layoutDoc[Height](),
+                NumCast(this.layoutDoc._width),
+                NumCast(this.layoutDoc._height),
                 this.props.PanelWidth(),
                 this.props.PanelHeight(),
                 NumCast(this.layoutDoc._layout_scrollTop),
@@ -255,8 +254,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.dataDoc[this.props.fieldKey + '_numPages'] = np;
         Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
         Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
-        this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1);
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
     };
 
     public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -331,7 +330,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -352,11 +351,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
         }
     });
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index ebb8a3374..b2512a0e5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DocCast, NumCast } from '../../../fields/Types';
@@ -18,16 +17,16 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { CaptureManager } from '../../util/CaptureManager';
 import { SettingsManager } from '../../util/SettingsManager';
+import { TrackMovements } from '../../util/TrackMovements';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { media_state } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
-import { TrackMovements } from '../../util/TrackMovements';
-import { media_state } from './AudioBox';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -145,7 +144,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (!nativeWidth || !nativeHeight) {
             if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200);
             Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect);
-            this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect;
+            this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
         }
     };
 
@@ -295,7 +294,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
         this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
     };
-    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth();
+    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d7f7c9b73..81749c98b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import { Doc, StrListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -355,8 +354,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     updateIcon = () => {
         const makeIcon = (returnedfilename: string) => {
             this.dataDoc.icon = new ImageField(returnedfilename);
-            this.dataDoc.icon_nativeWidth = this.layoutDoc[Width]();
-            this.dataDoc.icon_nativeHeight = this.layoutDoc[Height]();
+            this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width);
+            this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height);
         };
         this.Snapshot(undefined, undefined, makeIcon);
     };
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5c526fe38..a452b1cdd 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -3,7 +3,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
@@ -81,7 +80,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._url ? WebBox.urlHash(this._url) + '' : '';
     }
     @computed get scrollHeight() {
-        return Math.max(this.layoutDoc[Height](), this._scrollHeight);
+        return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
     }
     @computed get allAnnotations() {
         return DocListCast(this.dataDoc[this.annotationKey]);
@@ -209,7 +208,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
                     if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600);
                     Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect);
-                    this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect;
+                    this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
@@ -276,7 +275,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (anchor !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[Height](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[Height](), this._scrollHeight));
+            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
                     const focusTime = options.zoomTime ?? 500;
@@ -490,7 +489,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
                 if (this._scrollHeight) {
                     this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
-                    this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
                 }
             };
             const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
@@ -498,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth);
                 this.rootDoc.nativeWidth = swidth;
                 this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize;
-                this.layoutDoc.height = this.layoutDoc[Height]() * aspectResize;
+                this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
             }
             initHeights();
             this._iframetimeout && clearTimeout(this._iframetimeout);
@@ -838,7 +837,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -873,9 +872,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     toggleSidebar = action((preview: boolean = false) => {
         var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         if (!nativeWidth) {
-            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width]();
+            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width);
             Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
-            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / this.Document[Width]()) * defaultNativeWidth);
+            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth);
             nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         }
         const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
@@ -883,11 +882,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
                 this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined;
             } else {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41b1c59b0..515952ae4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import { setCORS } from 'google-translate-api-browser';
 import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -52,6 +53,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
+import { media_state } from '../AudioBox';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
@@ -71,8 +73,6 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { media_state } from '../AudioBox';
-import { setCORS } from 'google-translate-api-browser';
 // setting up cors-anywhere server address
 const translate = setCORS('http://cors-anywhere.herokuapp.com/');
 export const GoogleRef = 'googleDocId';
@@ -434,10 +434,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             var tr = this._editorView.state.tr as any;
             const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
-            DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+            Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
-            // this.prepareForTyping();
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
     };
@@ -460,7 +459,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
                 if (str.startsWith('@') && str.length > 1) {
-                    Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
+                    Doc.AddToMyPublished(this.rootDoc);
                 }
             }
         }
@@ -487,9 +486,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 ) ||
                                     DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
+                            // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
                             const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
-                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
                         }
                     });
@@ -579,8 +579,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     // embed document when drag marked as embed
                 } else if (de.embedKey) {
                     const node = schema.nodes.dashDoc.create({
-                        width: draggedDoc[Width](),
-                        height: draggedDoc[Height](),
+                        width: NumCast(draggedDoc._width),
+                        height: NumCast(draggedDoc._height),
                         title: 'dashDoc',
                         docId: draggedDoc[Id],
                         float: 'unset',
@@ -733,7 +733,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(this.props.NativeDimScaling?.() || 1)
             .transformDirection(delta[0], delta[1]);
         const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
-        const width = this.layoutDoc[Width]() + localDelta[0];
+        const width = NumCast(this.layoutDoc._width) + localDelta[0];
         this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
         this.layoutDoc.width = width;
         this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -1407,7 +1407,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
-                                location: 'add:right',
                                 title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
                                 noPreview: true,
                                 docref: false,
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6f07588b3..c565c4294 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -46,7 +46,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
-            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-noPreview': 'true', 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
         },
     },
     noAutoLinkAnchor: {
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 82ed9e8d5..0402516a2 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -3,7 +3,6 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -15,6 +14,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch } from '../../../util/UndoManager';
+import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -25,9 +25,6 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
-import { TreeView } from '../../collections/TreeView';
-import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { MultiToggle, Type } from 'browndash-components';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -331,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @computed get recordingIsInOverlay() {
-        return DocListCast(Doc.MyOverlayDocs.data).some(doc => doc.slides === this.rootDoc);
+        return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc);
     }
 
     // a previously recorded video will have timecode defined
@@ -341,14 +338,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        DocListCast(Doc.MyOverlayDocs.data)
-            .filter(doc => doc.slides === this.rootDoc)
-            .forEach(Doc.RemFromMyOverlay);
+        Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
         // Remove every recording that already exists in overlay view
-        DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+        Doc.MyOverlayDocs.forEach(doc => {
             if (doc.slides !== null) {
                 // if it's a recording video, don't remove from overlay (user can lose data)
                 if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) {
@@ -407,8 +402,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             activeItem.recording = recording;
 
             // make recording box appear in the bottom right corner of the screen
-            recording.overlayX = window.innerWidth - recording[Width]() - 20;
-            recording.overlayY = window.innerHeight - recording[Height]() - 20;
+            recording.overlayX = window.innerWidth - NumCast(recording._width) - 20;
+            recording.overlayY = window.innerHeight - NumCast(recording._height) - 20;
             Doc.AddToMyOverlay(recording);
         }
     };
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 17a8048e9..38e4f65b6 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -24,7 +24,7 @@ interface IAnnotationProps extends FieldViewProps {
 export class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return (
-            <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
+            <div style={{ display: this.props.anno.textCopied && !Doc.GetBrushHighlightStatus(this.props.anno) ? 'none' : undefined }}>
                 {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
                     <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
                 ))}
@@ -90,12 +90,12 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     @computed get linkHighlighted() {
         for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) {
             const a1 = LinkManager.getOppositeAnchor(link, this.props.document);
-            if (a1 && Doc.IsBrushedDegree(DocCast(a1.annotationOn, this.props.document))) return true;
+            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this.props.document))) return true;
         }
     }
 
     render() {
-        const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+        const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
         return (
             <div
                 className="htmlAnnotation"
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2f9eea492..6dece87b5 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,46 +1,23 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
-import { DocumentType } from '../client/documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
 import { LinkManager } from '../client/util/LinkManager';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
 import { undoable } from '../client/util/UndoManager';
+import { DocumentView } from '../client/views/nodes/DocumentView';
 import { decycle } from '../decycler/decycler';
 import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, intersectRect, Utils } from '../Utils';
+import { incrementTitleCopy, Utils } from '../Utils';
 import { DateField } from './DateField';
 import {
-    AclAdmin,
-    AclAugment,
-    AclEdit,
-    AclPrivate,
-    AclReadonly,
-    Animation,
-    AudioPlay,
-    Brushed,
-    CachedUpdates,
-    DirectLinks,
-    DocAcl,
-    DocCss,
-    DocData,
-    DocFields,
-    DocLayout,
-    DocViews,
-    FieldKeys,
-    FieldTuples,
-    ForceServerWrite,
-    Height,
-    Highlight,
-    Initializing,
-    Self,
-    SelfProxy,
-    UpdatingFromServer,
-    Width,
-} from './DocSymbols';
+    AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
+    DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
+    Initializing, Self, SelfProxy, UpdatingFromServer, Width
+} from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
 import { List, ListFieldName } from './List';
@@ -52,9 +29,8 @@ import { listSpec } from './Schema';
 import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, TraceMobx } from './util';
+import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import JSZip = require('jszip');
-import { DocumentView } from '../client/views/nodes/DocumentView';
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -66,25 +42,23 @@ export namespace Field {
                 : ''
             : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
-    export function toScriptString(field: Field): string {
+    export function toScriptString(field: Field) {
         switch (typeof field) {
-            case 'string':
-                if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
-                return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
+            case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
+                           return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
             case 'number':
-            case 'boolean':
-                return String(field);
-        }
-        return field?.[ToScriptString]?.() ?? 'null';
+            case 'boolean':return String(field);
+            default:       return field?.[ToScriptString]?.() ?? 'null';
+        } // prettier-ignore
     }
-    export function toString(field: Field): string {
+    export function toString(field: Field) {
         if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
         return field?.[ToString]?.() || '';
     }
     export function IsField(field: any): field is Field;
     export function IsField(field: any, includeUndefined: true): field is Field | undefined;
     export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
-        return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
+        return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
     }
     export function Copy(field: any) {
         return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
@@ -107,11 +81,6 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     const list = Cast(field, listSpec(Doc));
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
-
-export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
-    return Cast(field, Doc);
-}
-
 export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
     return Cast(field, listSpec('number'), defaultVal);
 }
@@ -162,140 +131,51 @@ export function updateCachedAcls(doc: Doc) {
 @Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
     @observable public static RecordingEvent = 0;
-
-    // this isn't really used at the moment, but is intended to indicate whether ink stroke are passed through a gesture recognizer
-    static GetRecognizeGestures() {
-        return BoolCast(Doc.UserDoc()._recognizeGestures);
-    }
-    static SetRecognizeGestures(show: boolean) {
-        Doc.UserDoc()._recognizeGestures = show;
-    }
-
-    //
-    // This controls whether fontIconButtons will display labels under their icons or not
-    //
-    static GetShowIconLabels() {
-        return BoolCast(Doc.UserDoc()._showLabel);
-    }
-    static SetShowIconLabels(show: boolean) {
-        Doc.UserDoc()._showLabel = show;
-    }
-    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
-    // removes from currently loading display
-    @action
-    public static removeCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading) {
-            const index = Doc.CurrentlyLoading.indexOf(doc);
-            index !== -1 && Doc.CurrentlyLoading.splice(index, 1);
-        }
-    }
-
-    // adds doc to currently loading display
-    @action
-    public static addCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading.indexOf(doc) === -1) {
-            Doc.CurrentlyLoading.push(doc);
-        }
-    }
-
     @observable public static GuestDashboard: Doc | undefined;
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestMobile: Doc | undefined;
-    public static get MySharedDocs() {
-        return DocCast(Doc.UserDoc().mySharedDocs);
-    }
-    public static get MyUserDocView() {
-        return DocCast(Doc.UserDoc().myUserDocView);
-    }
-    public static get MyDockedBtns() {
-        return DocCast(Doc.UserDoc().myDockedBtns);
-    }
-    public static get MySearcher() {
-        return DocCast(Doc.UserDoc().mySearcher);
-    }
-    public static get MyHeaderBar() {
-        return DocCast(Doc.UserDoc().myHeaderBar);
-    }
-    public static get MyLeftSidebarMenu() {
-        return DocCast(Doc.UserDoc().myLeftSidebarMenu);
-    }
-    public static get MyLeftSidebarPanel() {
-        return DocCast(Doc.UserDoc().myLeftSidebarPanel);
-    }
-    public static get MyContextMenuBtns() {
-        return DocCast(Doc.UserDoc().myContextMenuBtns);
-    }
-    public static get MyTopBarBtns() {
-        return DocCast(Doc.UserDoc().myTopBarBtns);
-    }
-    public static get MyRecentlyClosed() {
-        return DocCast(Doc.UserDoc().myRecentlyClosed);
-    }
-    public static get MyTrails() {
-        return DocCast(Doc.ActiveDashboard?.myTrails);
-    }
-    public static IsInMyOverlay(doc: Doc) {
-        return DocListCast(Doc.MyOverlayDocs?.data).includes(doc);
-    }
-    public static AddToMyOverlay(doc: Doc) {
-        Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static RemFromMyOverlay(doc: Doc) {
-        Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static get MyOverlayDocs() {
-        return DocCast(Doc.UserDoc().myOverlayDocs);
-    }
-    public static get MyPublishedDocs() {
-        return DocCast(Doc.UserDoc().myPublishedDocs);
-    }
-    public static get MyDashboards() {
-        return DocCast(Doc.UserDoc().myDashboards);
-    }
-    public static get MyTemplates() {
-        return DocCast(Doc.UserDoc().myTemplates);
-    }
-    public static get MyImports() {
-        return DocCast(Doc.UserDoc().myImports);
-    }
-    public static get MyFilesystem() {
-        return DocCast(Doc.UserDoc().myFilesystem);
-    }
-    public static get MyTools() {
-        return DocCast(Doc.UserDoc().myTools);
-    }
-    public static get ActivePage() {
-        return StrCast(Doc.UserDoc().activePage);
-    }
-    public static set ActivePage(val) {
-        Doc.UserDoc().activePage = val;
-        DocServer.UPDATE_SERVER_CACHE();
-    }
-    public static IsComicStyle(doc?: Doc) {
-        return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
-    }
-    public static get ActiveDashboard() {
-        return DocCast(Doc.UserDoc().activeDashboard);
-    }
-    public static set ActiveDashboard(val: Doc | undefined) {
-        const overlays = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), null);
-        overlays && (overlays.length = 0);
-        Doc.UserDoc().activeDashboard = val;
-    }
-    public static set ActiveTool(tool: InkTool) {
-        Doc.UserDoc().activeTool = tool;
-    }
-    public static get ActiveTool(): InkTool {
-        return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;
-    }
-    public static get ActivePresentation(): Opt<Doc> {
-        return DocCast(Doc.ActiveDashboard?.activePresentation);
-    }
-    public static set ActivePresentation(val) {
-        if (Doc.ActiveDashboard) {
-            Doc.ActiveDashboard.activePresentation = val;
-        }
-    }
+    public static CurrentUserEmail: string = '';
+
+    public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
+    public static get MyUserDocView()         { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
+    public static get MyDockedBtns()          { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore
+    public static get MySearcher()            { return DocCast(Doc.UserDoc().mySearcher); } // prettier-ignore
+    public static get MyHeaderBar()           { return DocCast(Doc.UserDoc().myHeaderBar); } // prettier-ignore
+    public static get MyLeftSidebarMenu()     { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } // prettier-ignore
+    public static get MyLeftSidebarPanel()    { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } // prettier-ignore
+    public static get MyContextMenuBtns()     { return DocCast(Doc.UserDoc().myContextMenuBtns); } // prettier-ignore
+    public static get MyTopBarBtns()          { return DocCast(Doc.UserDoc().myTopBarBtns); } // prettier-ignore
+    public static get MyRecentlyClosed()      { return DocCast(Doc.UserDoc().myRecentlyClosed); } // prettier-ignore
+    public static get MyTrails()              { return DocCast(Doc.ActiveDashboard?.myTrails); } // prettier-ignore
+    public static get MyOverlayDocs()         { return DocListCast(Doc.ActiveDashboard?.myOverlayDocs ?? DocCast(Doc.UserDoc().myOverlayDocs).data); } // prettier-ignore
+    public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs ?? DocCast(Doc.UserDoc().myPublishedDocs).data); } // prettier-ignore
+    public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
+    public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
+    public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
+    public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
+    public static get MyTools()               { return DocCast(Doc.UserDoc().myTools); } // prettier-ignore
+    public static get noviceMode()            { return BoolCast(Doc.UserDoc().noviceMode);  } // prettier-ignore
+    public static set noviceMode(val)         { Doc.UserDoc().noviceMode = val; } // prettier-ignore
+    public static get IsSharingEnabled()      { return BoolCast(Doc.UserDoc().isSharingEnabled); } // prettier-ignore
+    public static set IsSharingEnabled(val)   { Doc.UserDoc().isSharingEnabled = val; } // prettier-ignore
+    public static get defaultAclPrivate()     { return Doc.UserDoc().defaultAclPrivate; } // prettier-ignore
+    public static set defaultAclPrivate(val)  { Doc.UserDoc().defaultAclPrivate = val; } // prettier-ignore
+    public static get ActivePage()            { return StrCast(Doc.UserDoc().activePage); } // prettier-ignore
+    public static set ActivePage(val)         { Doc.UserDoc().activePage = val; } // prettier-ignore
+    public static get ActiveTool(): InkTool   { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;  } // prettier-ignore
+    public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore
+    public static get ActivePresentation()    { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>;  } // prettier-ignore
+    public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore
+    public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore
+    public static set ActiveDashboard(val: Opt<Doc>) {  Doc.UserDoc().activeDashboard = val; } // prettier-ignore
+
+    public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
+    public static AddToMyOverlay(doc: Doc)    { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyOverlay(doc: Doc)  { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc)  { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
+
     constructor(id?: FieldId, forceSave?: boolean) {
         super(id);
         const docProxy = new Proxy<this>(this, {
@@ -359,17 +239,14 @@ export class Doc extends RefField {
     @observable public [Highlight]: boolean = false;
     @observable public [Brushed]: boolean = false;
     @observable public [DocViews] = new ObservableSet<DocumentView>();
-    static __Anim(Doc: Doc) {
-        // for debugging to print AnimationSym field easily.
-        return Doc[Animation];
-    }
 
+    private [Self] = this;
+    private [SelfProxy]: any;
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
-    public [Initializing]: boolean = false;
+    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
 
-    private [Self] = this;
-    private [SelfProxy]: any;
+    public [Initializing]: boolean = false;
     public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
         if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
             DocServer.UpdateField(this[Id], serverOp);
@@ -380,9 +257,7 @@ export class Doc extends RefField {
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
-    public get [DocLayout]() {
-        return this[SelfProxy].__LAYOUT__;
-    }
+    public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
     public get [DocData](): Doc {
         const self = this[SelfProxy];
         return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
@@ -403,29 +278,6 @@ export class Doc extends RefField {
         return undefined;
     }
 
-    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
-    public static get noviceMode() {
-        return Doc.UserDoc().noviceMode as boolean;
-    }
-    public static set noviceMode(val) {
-        Doc.UserDoc().noviceMode = val;
-    }
-    public static get IsSharingEnabled() {
-        return Doc.UserDoc().isSharingEnabled as boolean;
-    }
-    public static set IsSharingEnabled(val) {
-        Doc.UserDoc().isSharingEnabled = val;
-    }
-    public static get defaultAclPrivate() {
-        return Doc.UserDoc().defaultAclPrivate;
-    }
-    public static set defaultAclPrivate(val) {
-        Doc.UserDoc().defaultAclPrivate = val;
-    }
-    public static CurrentUserEmail: string = '';
-    public static get CurrentUserEmailNormalized() {
-        return normalizeEmail(Doc.CurrentUserEmail);
-    }
     public async [HandleUpdate](diff: any) {
         const set = diff.$set;
         const sameAuthor = this.author === Doc.CurrentUserEmail;
@@ -482,17 +334,6 @@ export class Doc extends RefField {
 }
 
 export namespace Doc {
-    // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-    //     const self = doc[Self];
-    //     return new Promise(res => getField(self, key, ignoreProto, res));
-    // }
-    // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
-    //     return new Promise(async res => {
-    //         const field = await GetAsync(doc, key, ignoreProto);
-    //         return Cast(field, ctor);
-    //     });
-    // }
-
     export function SetContainer(doc: Doc, container: Doc) {
         doc.embedContainer = container;
     }
@@ -626,10 +467,9 @@ export namespace Doc {
     /**
      * @returns the index of doc toFind in list of docs, -1 otherwise
      */
-    export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
-        let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1);
-        index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1);
-        return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+    export function IndexOf(toFind: Doc, list: Doc[]) {
+        const index = list.indexOf(toFind);
+        return index !== -1 ? index : list.findIndex(doc => Doc.AreProtosEqual(doc, toFind));
     }
 
     /**
@@ -663,11 +503,10 @@ export namespace Doc {
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
-            if (allowDuplicates !== true) {
-                const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1);
+            if (!allowDuplicates) {
+                const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
                 if (pind !== -1) {
                     return true;
-                    //list.splice(pind, 1);  // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
                 }
             }
             if (first) {
@@ -687,22 +526,6 @@ export namespace Doc {
         return false;
     }
 
-    /**
-     * Computes the bounds of the contents of a set of documents.
-     */
-    export function ComputeContentBounds(docList: Doc[]) {
-        const bounds = docList.reduce(
-            (bounds, doc) => ({
-                x: Math.min(NumCast(doc.x), bounds.x),
-                y: Math.min(NumCast(doc.y), bounds.y),
-                r: Math.max(NumCast(doc.x) + doc[Width](), bounds.r),
-                b: Math.max(NumCast(doc.y) + doc[Height](), bounds.b),
-            }),
-            { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
-        );
-        return bounds;
-    }
-
     export function MakeEmbedding(doc: Doc, id?: string) {
         const embedding = !GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(embedding);
@@ -779,7 +602,7 @@ export namespace Doc {
                 } else if (field instanceof RefField) {
                     assignKey(field);
                 } else if (cfield instanceof ComputedField) {
-                    assignKey(cfield[Copy]()); // ComputedField.MakeFunction(cfield.script.originalScript));
+                    assignKey(cfield[Copy]());
                 } else if (field instanceof ObjectField) {
                     await copyObjectField(field);
                 } else if (field instanceof Promise) {
@@ -990,29 +813,6 @@ export namespace Doc {
         return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
     }
 
-    export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
-        Object.keys(doc).forEach(key => {
-            const field = ProxyField.WithoutProxy(() => doc[key]);
-            if (key === 'proto' && copyProto) {
-                if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
-                    overwrite[key] = Doc.Overwrite(doc.proto, overwrite.proto);
-                }
-            } else {
-                if (field instanceof RefField) {
-                    overwrite[key] = field;
-                } else if (field instanceof ObjectField) {
-                    overwrite[key] = ObjectField.MakeCopy(field);
-                } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happend...
-                } else {
-                    overwrite[key] = field;
-                }
-            }
-        });
-
-        return overwrite;
-    }
-
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
         if (infield instanceof List<any>) {
             infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
@@ -1217,22 +1017,8 @@ export namespace Doc {
         return '/doc/' + (doc ? doc[Id] : '');
     }
 
-    export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
-        const doc2Layout = Doc.Layout(doc2);
-        const doc1Layout = Doc.Layout(doc1);
-        const x2 = NumCast(doc2.x) - clusterDistance;
-        const y2 = NumCast(doc2.y) - clusterDistance;
-        const w2 = NumCast(doc2Layout._width) + clusterDistance;
-        const h2 = NumCast(doc2Layout._height) + clusterDistance;
-        const x = NumCast(doc1.x) - clusterDistance;
-        const y = NumCast(doc1.y) - clusterDistance;
-        const w = NumCast(doc1Layout._width) + clusterDistance;
-        const h = NumCast(doc1Layout._height) + clusterDistance;
-        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
-    }
-
-    export function isBrushedHighlightedDegree(doc: Doc) {
-        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
+    export function GetBrushHighlightStatus(doc: Doc) {
+        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.GetBrushStatus(doc);
     }
     export class DocBrush {
         BrushedDoc = new Set<Doc>();
@@ -1269,12 +1055,12 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[Width]() : 0));
+        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
-        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[Height]()) / doc[Width]();
-        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[Height]() : 0);
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * NumCast(doc._height)) / NumCast(doc._width);
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
@@ -1337,36 +1123,22 @@ export namespace Doc {
         highlighted = 3,
     }
     // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
-    export function IsBrushedDegree(doc: Doc) {
+    export function GetBrushStatus(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
         return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
     export function BrushDoc(doc: Doc, unbrush = false) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        brushManager.brushDoc(doc, unbrush);
-        brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+            brushManager.brushDoc(doc, unbrush);
+            brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+        }
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         return BrushDoc(doc, true);
     }
-
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
-        const linkAnchor2 = DocCast(linkDoc.link_anchor_2);
-        const linkAnchor1 = DocCast(linkDoc.link_anchor_1);
-        if (linkDoc.link_matchEmbeddings) {
-            return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1';
-        }
-        if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2';
-        return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2';
-    }
-
-    export function linkFollowUnhighlight() {
-        clearTimeout(UnhighlightTimer);
-        UnhighlightWatchers.forEach(watcher => watcher());
-        UnhighlightWatchers.length = 0;
-        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
-        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    export function UnBrushAllDocs() {
+        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
     }
 
     let UnhighlightWatchers: (() => void)[] = [];
@@ -1376,6 +1148,13 @@ export namespace Doc {
             UnhighlightWatchers.push(watcher);
         } else watcher();
     }
+    export function linkFollowUnhighlight() {
+        clearTimeout(UnhighlightTimer);
+        UnhighlightWatchers.forEach(watcher => watcher());
+        UnhighlightWatchers.length = 0;
+        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
+        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    }
     export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
         linkFollowUnhighlight();
         (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
@@ -1415,9 +1194,6 @@ export namespace Doc {
             });
         });
     }
-    export function UnBrushAllDocs() {
-        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
-    }
 
     export function getDocTemplate(doc?: Doc) {
         return !doc
@@ -1433,41 +1209,6 @@ export namespace Doc {
             : undefined;
     }
 
-    export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
-        const hasFunctionFilter = Utils.HasFunctionFilter(value);
-        if (hasFunctionFilter) {
-            return hasFunctionFilter(StrCast(doc[key]));
-        }
-        if (key === LinkedTo) {
-            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
-            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
-            const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = value?.split('=');
-                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
-                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
-            };
-            // prettier-ignore
-            return  (value === Doc.FilterNone && !allLinks.length) || 
-                    (value === Doc.FilterAny && !!allLinks.length) ||
-                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
-                                           matchLink(value,DocCast(link.link_anchor_2)) ));
-        }
-        if (typeof value === 'string') {
-            value = value.replace(`,${Utils.noRecursionHack}`, '');
-        }
-        const fieldVal = doc[key];
-        // prettier-ignore
-        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
-            (value === Doc.FilterNone && fieldVal === undefined)) {
-            return true;
-        }
-        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
-        if (vals.length) {
-            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-        }
-        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-    }
-
     export function deiconifyView(doc: Doc) {
         StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc);
     }
@@ -1603,15 +1344,25 @@ export namespace Doc {
     }
 
     // prettier-ignore
-    export function toIcon(doc?: Doc, isOpen?: boolean) {
-        switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) {
+    export function toIcon(doc?: Doc, isOpen?: Opt<boolean>) {
+        if (isOpen) return doc?.isFolder ? 'chevron-down' : 'folder-open';
+        switch (StrCast(doc?.type)) {
             case DocumentType.IMG:  return 'image';
             case DocumentType.COMPARISON: return 'columns';
             case DocumentType.RTF: return 'sticky-note';
             case DocumentType.COL:
-                const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc?.title==='Untitled Collection'? 'object-group': 'chalkboard';
-                const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question';
-                return !doc?.isFolder ? folder : chevron;
+                if (doc?.isFolder) {
+                    switch (doc.type_collection) {
+                        default: return isOpen === false ? 'chevron-right' : 'question';
+                    } // prettier-ignore
+                }
+                switch (doc?.type_collection) {
+                    case CollectionViewType.Freeform :   return 'object-group';
+                    case CollectionViewType.NoteTaking : return 'chalkboard';
+                    case CollectionViewType.Schema :     return 'table-cells';
+                    case CollectionViewType.Docking:     return 'solar-panel';
+                    default:                             return 'folder';
+                } // prettier-ignore
             case DocumentType.WEB: return 'globe-asia';
             case DocumentType.SCREENSHOT: return 'photo-video';
             case DocumentType.WEBCAM: return 'video';
@@ -1628,9 +1379,9 @@ export namespace Doc {
             case DocumentType.DATAVIZ: return 'chart-bar';
             case DocumentType.EQUATION: return 'calculator';
             case DocumentType.SIMULATION: return 'rocket';
-            case DocumentType.CONFIG: return 'question-circle';
-            default: return 'question';
+            case DocumentType.CONFIG: return 'folder-closed';
         }
+        return 'question';
     }
 
     ///
@@ -1839,14 +1590,13 @@ ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
 ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
     return Doc.assignDocToField(doc, field, id);
 });
-ScriptingGlobals.add(function docCast(doc: FieldResult): any {
-    return DocCastAsync(doc);
+ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
+    return Cast(doc, Doc);
 });
 ScriptingGlobals.add(function activePresentationItem() {
     const curPres = Doc.ActivePresentation;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
-
 ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
     Doc.setDocFilter(container, key, value, modifiers);
 });
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 5ecf25e08..dfd02dbc0 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -275,7 +275,7 @@ export namespace RichTextUtils {
             } else {
                 docId = backingDocId;
             }
-            return schema.node('image', { src, agnostic, width, docId, float: null, location: 'add:right' });
+            return schema.node('image', { src, agnostic, width, docId, float: null });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 28db77c65..ca02284da 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -234,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
  * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
  */
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
-    const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
+    const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
     if (!visited) visited = [] as Doc[];
     if (!target || visited.includes(target) || key === selfKey) return;
     visited.push(target);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 498bec6ed..e24bcd733 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -658,7 +658,7 @@ export class MobileInterface extends React.Component {
     // DocButton for switching into ink mode
     @computed get drawInk() {
         return !this.mainContainer || this._activeDoc._type_collection !== CollectionViewType.Docking ? null : (
-            <div className="docButton" id="inkButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.onSwitchInking}>
+            <div className="docButton" id="inkButton" onClick={this.onSwitchInking}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
             </div>
         );
@@ -668,7 +668,7 @@ export class MobileInterface extends React.Component {
     @computed get uploadImageButton() {
         if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') {
             return (
-                <div className="docButton" id="imageButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.toggleUpload}>
+                <div className="docButton" id="imageButton" onClick={this.toggleUpload}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
                 </div>
             );
@@ -692,13 +692,8 @@ export class MobileInterface extends React.Component {
     @computed get pinToPresentation() {
         // Only making button available if it is an image
         if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) {
-            const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
             return (
-                <div
-                    className="docButton"
-                    title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
-                    style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }}
-                    onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
+                <div className="docButton" title={'Pin to presentation'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             );
-- 
cgit v1.2.3-70-g09d2


From 0b38b0629496973d6c4571208710096deb91b7d7 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Fri, 24 Nov 2023 17:59:13 -0500
Subject: merge

---
 package-lock.json                                  |   8 +-
 src/Utils.ts                                       |  31 +-
 src/client/cognitive_services/CognitiveServices.ts | 195 +++---
 src/client/documents/Documents.ts                  | 131 +++-
 src/client/goldenLayout.js                         |   6 +-
 src/client/util/CaptureManager.tsx                 |   2 +-
 src/client/util/CurrentUserUtils.ts                |  14 +-
 src/client/util/DocumentManager.ts                 |  12 +-
 src/client/util/DragManager.ts                     |  26 +-
 src/client/util/LinkManager.ts                     |  32 +-
 src/client/util/SelectionManager.ts                |  63 +-
 src/client/util/SettingsManager.tsx                |  12 +-
 src/client/util/SnappingManager.ts                 |  14 +
 src/client/util/Transform.ts                       |  73 ++-
 src/client/views/DashboardView.tsx                 |   5 +-
 src/client/views/DocComponent.tsx                  |  31 +-
 src/client/views/DocumentDecorations.scss          |  24 +-
 src/client/views/DocumentDecorations.tsx           | 677 +++++++++------------
 src/client/views/GestureOverlay.tsx                | 465 +-------------
 src/client/views/GlobalKeyHandler.ts               | 130 ++--
 src/client/views/InkControlPtHandles.tsx           |   7 +-
 src/client/views/InkStrokeProperties.ts            |   4 +-
 src/client/views/InkTranscription.tsx              |   9 +-
 src/client/views/InkingStroke.tsx                  |  11 +-
 src/client/views/LightboxView.tsx                  | 433 ++++++-------
 src/client/views/MainView.tsx                      |  38 +-
 src/client/views/MarqueeAnnotator.tsx              | 286 ++++-----
 src/client/views/OverlayView.tsx                   | 128 ++--
 src/client/views/PropertiesView.tsx                |  16 +-
 src/client/views/StyleProvider.tsx                 |  10 +-
 src/client/views/animationtimeline/Track.tsx       |   3 +
 .../views/collections/CollectionDockingView.scss   |  20 +-
 .../views/collections/CollectionDockingView.tsx    |  15 +-
 src/client/views/collections/CollectionMenu.tsx    |  27 +-
 .../views/collections/CollectionNoteTakingView.tsx |  14 +-
 .../views/collections/CollectionPileView.tsx       |  17 +-
 .../collections/CollectionStackedTimeline.tsx      |   8 +-
 .../views/collections/CollectionStackingView.tsx   |  14 +-
 .../views/collections/CollectionStaffView.scss     |  13 -
 .../views/collections/CollectionStaffView.tsx      |  53 --
 src/client/views/collections/CollectionSubView.tsx |  15 +-
 .../views/collections/CollectionTreeView.scss      |   1 -
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 src/client/views/collections/CollectionView.tsx    |  38 +-
 src/client/views/collections/TabDocView.scss       |   1 -
 src/client/views/collections/TabDocView.tsx        |  92 +--
 src/client/views/collections/TreeView.tsx          |  20 +-
 .../CollectionFreeFormBackgroundGrid.tsx           |  75 +++
 .../CollectionFreeFormLayoutEngines.tsx            |  17 +-
 .../CollectionFreeFormLinkView.tsx                 |  20 +-
 .../CollectionFreeFormPannableContents.tsx         |  60 ++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 660 ++++++--------------
 .../collections/collectionFreeForm/MarqueeView.tsx |  46 +-
 .../collectionGrid/CollectionGridView.tsx          |   2 +-
 .../collectionLinear/CollectionLinearView.tsx      |   2 +-
 .../CollectionMulticolumnView.tsx                  |   2 +-
 .../CollectionMultirowView.tsx                     |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      | 136 +++--
 .../collections/collectionSchema/SchemaRowBox.tsx  |  22 +-
 src/client/views/global/globalCssVariables.scss    |   2 -
 .../views/global/globalCssVariables.scss.d.ts      |   1 -
 src/client/views/global/globalScripts.ts           |  47 +-
 .../nodes/CollectionFreeFormDocumentView.scss      |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 222 ++++---
 src/client/views/nodes/ColorBox.tsx                |  19 +-
 src/client/views/nodes/ComparisonBox.tsx           |   1 -
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  80 ++-
 .../views/nodes/DataVizBox/components/PieChart.tsx |   6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   4 +-
 src/client/views/nodes/DocumentView.scss           |  10 +-
 src/client/views/nodes/DocumentView.tsx            | 199 +++---
 src/client/views/nodes/EquationBox.tsx             |  17 +-
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  11 +-
 src/client/views/nodes/ImageBox.scss               |   1 +
 src/client/views/nodes/ImageBox.tsx                |  24 +-
 src/client/views/nodes/KeyValuePair.tsx            |   4 +-
 src/client/views/nodes/LinkBox.tsx                 |  17 +-
 src/client/views/nodes/LinkDocPreview.tsx          |  30 +-
 src/client/views/nodes/LoadingBox.tsx              |  21 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |   2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  54 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |  53 +-
 src/client/views/nodes/PDFBox.tsx                  |  29 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   9 +-
 src/client/views/nodes/ScriptingBox.tsx            |   1 -
 src/client/views/nodes/VideoBox.scss               |   1 +
 src/client/views/nodes/VideoBox.tsx                |  32 +-
 src/client/views/nodes/WebBox.tsx                  | 207 ++++---
 src/client/views/nodes/WebBoxRenderer.js           | 113 ++--
 .../views/nodes/formattedText/DashDocView.tsx      |   8 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   1 +
 .../nodes/formattedText/FormattedTextBox.scss      |   2 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |  80 +--
 .../views/nodes/formattedText/RichTextMenu.tsx     |  12 +-
 src/client/views/nodes/formattedText/marks_rts.ts  |   8 +-
 src/client/views/nodes/formattedText/nodes_rts.ts  |   1 -
 src/client/views/nodes/trails/PresBox.tsx          |  53 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |  19 +-
 src/client/views/pdf/AnchorMenu.tsx                |   2 +-
 src/client/views/pdf/Annotation.tsx                |  36 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  25 +
 src/client/views/pdf/PDFViewer.tsx                 |  63 +-
 src/fields/Doc.ts                                  | 537 ++++------------
 src/fields/DocSymbols.ts                           |   4 +-
 src/fields/RichTextUtils.ts                        |   2 +-
 src/fields/Types.ts                                |   3 +
 src/fields/util.ts                                 |   2 +-
 src/mobile/MobileInterface.tsx                     |  11 +-
 110 files changed, 2638 insertions(+), 3750 deletions(-)
 delete mode 100644 src/client/views/collections/CollectionStaffView.scss
 delete mode 100644 src/client/views/collections/CollectionStaffView.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
 create mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index 4b49c330b..531193ef6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27778,7 +27778,13 @@
         "tweetnacl": {
             "version": "0.14.5",
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
-            "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
+            "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        },
+        "type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+            "dev": true
         },
         "type-check": {
             "version": "0.4.0",
diff --git a/src/Utils.ts b/src/Utils.ts
index 330ca59f9..4e4414a93 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -157,23 +157,19 @@ export namespace Utils {
 
     const isTransparentFunctionHack = 'isTransparent(__value__)';
     export const noRecursionHack = '__noRecursion';
-    export const noDragsDocFilter = 'noDragDocs::any::check';
+
+    // special case filters
+    export const noDragDocsFilter = 'noDragDocs::any::check';
+    export const TransparentBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
+    export const OpaqueBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
+
     export function IsRecursiveFilter(val: string) {
         return !val.includes(noRecursionHack);
     }
-    export function HasTransparencyFilter(val: string) {
-        return val.includes(isTransparentFunctionHack);
-    }
-    export function IsTransparentFilter() {
-        // bcz: isTransparent(__value__) is a hack.  it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
-        return `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
-    }
-    export function IsOpaqueFilter() {
-        // bcz: isTransparent(__value__) is a hack.  it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
-        return `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack.  noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field
-    }
-    export function IsPropUnsetFilter(prop: string) {
-        return `${prop}::any,${noRecursionHack}::unset`;
+    export function HasFunctionFilter(val: string) {
+        if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1;
+        // add other function filters here...
+        return undefined;
     }
 
     export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
@@ -758,12 +754,9 @@ export function DashColor(color: string) {
 }
 
 export function lightOrDark(color: any) {
-    if (color === 'transparent' || !color) return Colors.DARK_GRAY;
+    if (color === 'transparent' || !color) return Colors.BLACK;
     if (color.startsWith?.('linear')) return Colors.BLACK;
-    const nonAlphaColor = color.startsWith('#') ? (color as string).substring(0, 7) : color.startsWith('rgba') ? color.replace(/,.[^,]*\)/, ')').replace('rgba', 'rgb') : color;
-    const col = DashColor(nonAlphaColor).rgb();
-    const colsum = col.red() + col.green() + col.blue();
-    if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
+    if (DashColor(color).isLight()) return Colors.BLACK;
     return Colors.WHITE;
 }
 
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 2b2931a97..408903324 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,29 +1,29 @@
-import * as request from "request-promise";
-import { Doc, Field } from "../../fields/Doc";
-import { Cast } from "../../fields/Types";
-import { Utils } from "../../Utils";
-import { InkData } from "../../fields/InkField";
-import { UndoManager } from "../util/UndoManager";
-import requestPromise = require("request-promise");
-import { List } from "../../fields/List";
-
-type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
+import * as request from 'request-promise';
+import { Doc, Field } from '../../fields/Doc';
+import { Cast } from '../../fields/Types';
+import { Utils } from '../../Utils';
+import { InkData } from '../../fields/InkField';
+import { UndoManager } from '../util/UndoManager';
+import requestPromise = require('request-promise');
+import { List } from '../../fields/List';
+
+type APIManager<D> = { converter: BodyConverter<D>; requester: RequestExecutor };
 type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
 type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
 type BodyConverter<D> = (data: D) => string;
 type Converter = (results: any) => Field;
-type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, external_recommendations: any, kp_string: string[] }>;
-type BingConverter = (results: any) => Promise<{ title_vals: string[], url_vals: string[] }>;
+type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field; external_recommendations: any; kp_string: string[] }>;
+type BingConverter = (results: any) => Promise<{ title_vals: string[]; url_vals: string[] }>;
 
-export type Tag = { name: string, confidence: number };
-export type Rectangle = { top: number, left: number, width: number, height: number };
+export type Tag = { name: string; confidence: number };
+export type Rectangle = { top: number; left: number; width: number; height: number };
 
 export enum Service {
-    ComputerVision = "vision",
-    Face = "face",
-    Handwriting = "handwriting",
-    Text = "text",
-    Bing = "bing"
+    ComputerVision = 'vision',
+    Face = 'face',
+    Handwriting = 'handwriting',
+    Text = 'text',
+    Bing = 'bing',
 }
 
 export enum Confidence {
@@ -32,7 +32,7 @@ export enum Confidence {
     Poor = 0.4,
     Fair = 0.6,
     Good = 0.8,
-    Excellent = 0.95
+    Excellent = 0.95,
 }
 
 /**
@@ -41,13 +41,8 @@ export enum Confidence {
  * various media types.
  */
 export namespace CognitiveServices {
-
     const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
         let apiKey = process.env[service.toUpperCase()];
-        // A HACK FOR A DEMO VIDEO - syip2
-        if (service === "handwriting") {
-            apiKey = "61088486d76c4b12ba578775a5f55422";
-        }
         if (!apiKey) {
             console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`);
             return undefined;
@@ -64,9 +59,7 @@ export namespace CognitiveServices {
     };
 
     export namespace Image {
-
         export const Manager: APIManager<string> = {
-
             converter: (imageUrl: string) => JSON.stringify({ url: imageUrl }),
 
             requester: async (apiKey: string, body: string, service: Service) => {
@@ -77,18 +70,17 @@ export namespace CognitiveServices {
                     case Service.Face:
                         uriBase = 'face/v1.0/detect';
                         parameters = {
-                            'returnFaceId': 'true',
-                            'returnFaceLandmarks': 'false',
-                            'returnFaceAttributes': 'age,gender,headPose,smile,facialHair,glasses,' +
-                                'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise'
+                            returnFaceId: 'true',
+                            returnFaceLandmarks: 'false',
+                            returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,' + 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise',
                         };
                         break;
                     case Service.ComputerVision:
                         uriBase = 'vision/v2.0/analyze';
                         parameters = {
-                            'visualFeatures': 'Categories,Description,Color,Objects,Tags,Adult',
-                            'details': 'Celebrities,Landmarks',
-                            'language': 'en',
+                            visualFeatures: 'Categories,Description,Color,Objects,Tags,Adult',
+                            details: 'Celebrities,Landmarks',
+                            language: 'en',
                         };
                         break;
                 }
@@ -99,69 +91,63 @@ export namespace CognitiveServices {
                     body: body,
                     headers: {
                         'Content-Type': 'application/json',
-                        'Ocp-Apim-Subscription-Key': apiKey
-                    }
+                        'Ocp-Apim-Subscription-Key': apiKey,
+                    },
                 };
 
                 return request.post(options);
             },
-
         };
 
         export namespace Appliers {
-
             export const ProcessImage: AnalysisApplier<string> = async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => {
-                const batch = UndoManager.StartBatch("Image Analysis");
+                const batch = UndoManager.StartBatch('Image Analysis');
 
                 const storageKey = keys[0];
-                if (!url || await Cast(target[storageKey], Doc)) {
+                if (!url || (await Cast(target[storageKey], Doc))) {
                     return;
                 }
                 let toStore: any;
                 const results = await ExecuteQuery(service, Manager, url);
                 if (!results) {
-                    toStore = "Cognitive Services could not process the given image URL.";
+                    toStore = 'Cognitive Services could not process the given image URL.';
                 } else {
                     if (!results.length) {
                         toStore = converter(results);
                     } else {
-                        toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+                        toStore = results.length > 0 ? converter(results) : 'Empty list returned.';
                     }
                 }
                 target[storageKey] = toStore;
 
                 batch.end();
             };
-
         }
 
-        export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
-
+        export type Face = { faceAttributes: any; faceId: string; faceRectangle: Rectangle };
     }
 
     export namespace Inking {
-
         export const Manager: APIManager<InkData[]> = {
-
             converter: (inkData: InkData[]): string => {
                 let id = 0;
                 const strokes: AzureStrokeData[] = inkData.map(points => ({
                     id: id++,
-                    points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(","),
-                    language: "en-US"
+                    points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(','),
+                    language: 'en-US',
                 }));
                 return JSON.stringify({
                     version: 1,
-                    language: "en-US",
-                    unit: "mm",
-                    strokes
+                    language: 'en-US',
+                    unit: 'mm',
+                    strokes,
                 });
             },
 
             requester: async (apiKey: string, body: string) => {
                 const xhttp = new XMLHttpRequest();
-                const serverAddress = "https://api.cognitive.microsoft.com";
-                const endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+                const serverAddress = 'https://api.cognitive.microsoft.com';
+                const endpoint = serverAddress + '/inkrecognizer/v1.0-preview/recognize';
 
                 return new Promise<string>((resolve, reject) => {
                     xhttp.onreadystatechange = function () {
@@ -177,7 +163,7 @@ export namespace CognitiveServices {
                         }
                     };
 
-                    xhttp.open("PUT", endpoint, true);
+                    xhttp.open('PUT', endpoint, true);
                     xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                     xhttp.setRequestHeader('Content-Type', 'application/json');
                     xhttp.send(body);
@@ -186,18 +172,17 @@ export namespace CognitiveServices {
         };
 
         export namespace Appliers {
-
             export const ConcatenateHandwriting: AnalysisApplier<InkData[]> = async (target: Doc, keys: string[], inkData: InkData[]) => {
-                const batch = UndoManager.StartBatch("Ink Analysis");
+                const batch = UndoManager.StartBatch('Ink Analysis');
 
                 let results = await ExecuteQuery(Service.Handwriting, Manager, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
-                    target[keys[0]] = Doc.Get.FromJson({ data: results, title: "Ink Analysis" });
+                    target[keys[0]] = Doc.Get.FromJson({ data: results, title: 'Ink Analysis' });
                     const recognizedText = results.map((item: any) => item.recognizedText);
                     const recognizedObjects = results.map((item: any) => item.recognizedObject);
-                    const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
-                    target[keys[1]] = individualWords.length ? individualWords.join(" ") : recognizedObjects.join(", ");
+                    const individualWords = recognizedText.filter((text: string) => text && text.split(' ').length === 1);
+                    target[keys[1]] = individualWords.length ? individualWords.join(' ') : recognizedObjects.join(', ');
                 }
 
                 batch.end();
@@ -224,7 +209,6 @@ export namespace CognitiveServices {
             unit: string;
             strokes: AzureStrokeData[];
         }
-
     }
 
     export namespace BingSearch {
@@ -234,7 +218,7 @@ export namespace CognitiveServices {
             },
             requester: async (apiKey: string, query: string) => {
                 const xhttp = new XMLHttpRequest();
-                const serverAddress = "https://api.cognitive.microsoft.com";
+                const serverAddress = 'https://api.cognitive.microsoft.com';
                 const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
                 const promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
@@ -251,29 +235,26 @@ export namespace CognitiveServices {
                     };
 
                     if (apiKey) {
-                        xhttp.open("GET", endpoint, true);
+                        xhttp.open('GET', endpoint, true);
                         xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                         xhttp.setRequestHeader('Content-Type', 'application/json');
                         xhttp.send();
-                    }
-                    else {
-                        console.log("API key for BING unavailable");
+                    } else {
+                        console.log('API key for BING unavailable');
                     }
                 };
                 return new Promise<any>(promisified);
-            }
-
+            },
         };
 
         export namespace Appliers {
             export const analyzer = async (query: string, converter: BingConverter) => {
                 const results = await ExecuteQuery(Service.Bing, Manager, query);
-                console.log("Bing results: ", results);
+                console.log('Bing results: ', results);
                 const { title_vals, url_vals } = await converter(results);
                 return { title_vals, url_vals };
             };
         }
-
     }
 
     export namespace HathiTrust {
@@ -283,7 +264,7 @@ export namespace CognitiveServices {
             },
             requester: async (apiKey: string, query: string) => {
                 const xhttp = new XMLHttpRequest();
-                const serverAddress = "https://babel.hathitrust.org/cgi/htd/​";
+                const serverAddress = 'https://babel.hathitrust.org/cgi/htd/​';
                 const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
                 const promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
@@ -300,54 +281,52 @@ export namespace CognitiveServices {
                     };
 
                     if (apiKey) {
-                        xhttp.open("GET", endpoint, true);
+                        xhttp.open('GET', endpoint, true);
                         xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                         xhttp.setRequestHeader('Content-Type', 'application/json');
                         xhttp.send();
-                    }
-                    else {
-                        console.log("API key for BING unavailable");
+                    } else {
+                        console.log('API key for BING unavailable');
                     }
                 };
                 return new Promise<any>(promisified);
-            }
-
+            },
         };
 
         export namespace Appliers {
             export const analyzer = async (query: string, converter: BingConverter) => {
                 const results = await ExecuteQuery(Service.Bing, Manager, query);
-                console.log("Bing results: ", results);
+                console.log('Bing results: ', results);
                 const { title_vals, url_vals } = await converter(results);
                 return { title_vals, url_vals };
             };
         }
-
     }
 
-
     export namespace Text {
         export const Manager: APIManager<string> = {
             converter: (data: string) => {
                 return JSON.stringify({
-                    documents: [{
-                        id: 1,
-                        language: "en",
-                        text: data
-                    }]
+                    documents: [
+                        {
+                            id: 1,
+                            language: 'en',
+                            text: data,
+                        },
+                    ],
                 });
             },
             requester: async (apiKey: string, body: string, service: Service) => {
-                const serverAddress = "https://eastus.api.cognitive.microsoft.com";
-                const endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases";
+                const serverAddress = 'https://eastus.api.cognitive.microsoft.com';
+                const endpoint = serverAddress + '/text/analytics/v2.1/keyPhrases';
                 const sampleBody = {
-                    "documents": [
+                    documents: [
                         {
-                            "language": "en",
-                            "id": 1,
-                            "text": "Hello world. This is some input text that I love."
-                        }
-                    ]
+                            language: 'en',
+                            id: 1,
+                            text: 'Hello world. This is some input text that I love.',
+                        },
+                    ],
                 };
                 const actualBody = body;
                 const options = {
@@ -355,25 +334,23 @@ export namespace CognitiveServices {
                     body: actualBody,
                     headers: {
                         'Content-Type': 'application/json',
-                        'Ocp-Apim-Subscription-Key': apiKey
-                    }
-
+                        'Ocp-Apim-Subscription-Key': apiKey,
+                    },
                 };
                 return request.post(options);
-            }
+            },
         };
 
         export namespace Appliers {
-
             export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
-                console.log("vectorizing...");
+                console.log('vectorizing...');
                 //keyterms = ["father", "king"];
 
-                const args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
-                await requestPromise.post(args).then(async (wordvecs) => {
+                const args = { method: 'POST', uri: Utils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
+                await requestPromise.post(args).then(async wordvecs => {
                     if (wordvecs) {
                         const indices = Object.keys(wordvecs);
-                        console.log("successful vectorization!");
+                        console.log('successful vectorization!');
                         const vectorValues = new List<number>();
                         indices.forEach((ind: any) => {
                             vectorValues.push(wordvecs[ind]);
@@ -381,15 +358,14 @@ export namespace CognitiveServices {
                         //ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
                     } // adds document to internal doc set
                     else {
-                        console.log("unsuccessful :( word(s) not in vocabulary");
+                        console.log('unsuccessful :( word(s) not in vocabulary');
                     }
-                }
-                );
+                });
             }
 
             export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => {
                 const results = await ExecuteQuery(Service.Text, Manager, data);
-                console.log("Cognitive Services keyphrases: ", results);
+                console.log('Cognitive Services keyphrases: ', results);
                 const { keyterms, external_recommendations, kp_string } = await converter(results, data);
                 target[keys[0]] = keyterms;
                 if (isInternal) {
@@ -400,10 +376,7 @@ export namespace CognitiveServices {
                 }
             };
 
-            // export async function countFrequencies() 
+            // export async function countFrequencies()
         }
-
     }
-
-
-}
\ No newline at end of file
+}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4086ede20..029653204 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { action, reaction, runInAction } from 'mobx';
 import { basename } from 'path';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, Field, LinkedTo, Opt, updateCachedAcls } from '../../fields/Doc';
+import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc';
 import { Initializing } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { HtmlField } from '../../fields/HtmlField';
@@ -186,8 +186,6 @@ export class DocumentOptions {
     linearBtnWidth?: NUMt = new NumInfo('unexpanded width of a linear menu button (button "width" changes when it expands)', false);
     _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false);
     _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false);
-    _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
-    _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
 
     'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions
     '_acl-Guest'?: string; // public permissions
@@ -229,12 +227,16 @@ export class DocumentOptions {
     layout_hideLinkButton?: BOOLt = new BoolInfo('whether the blue link counter button should be hidden');
     layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected');
     layout_borderRounding?: string;
+    _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
+    _layout_reflowVertical?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
+    _layout_reflowHorizontal?: BOOLt = new BoolInfo('whether a doc with a native size can be horizonally resized, causing some form of reflow');
     layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
     layout_maxAutoHeight?: NUMt = new NumInfo('maximum height for newly created (eg, from pasting) text documents', false);
     _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
     _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
     _layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video)  value is in seconds', false);
     _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
+    _layout_centered?: BOOLt = new BoolInfo('whether text should be vertically centered in Doc');
     _layout_fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
     _layout_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content');
     _layout_fieldKey?: STRt = new StrInfo('the field key containing the current layout definition', false);
@@ -472,9 +474,9 @@ export namespace Docs {
                         _height: 35,
                         _xMargin: 10,
                         _yMargin: 10,
-                        nativeDimModifiable: true,
-                        nativeHeightUnfrozen: true,
-                        layout_forceReflow: true,
+                        layout_nativeDimEditable: true,
+                        layout_reflowVertical: true,
+                        layout_reflowHorizontal: true,
                         defaultDoubleClick: 'ignore',
                         systemIcon: 'BsFileEarmarkTextFill',
                     },
@@ -505,14 +507,24 @@ export namespace Docs {
                 DocumentType.WEB,
                 {
                     layout: { view: WebBox, dataField: defaultDataKey },
-                    options: { _height: 300, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
+                    options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' },
                 },
             ],
             [
                 DocumentType.COL,
                 {
                     layout: { view: CollectionView, dataField: defaultDataKey },
-                    options: { _layout_fitWidth: true, freeform: '', _freeform_panX: 0, _freeform_panY: 0, _freeform_scale: 1, systemIcon: 'BsFillCollectionFill' },
+                    options: {
+                        _layout_fitWidth: true,
+                        freeform: '',
+                        _freeform_panX: 0,
+                        _freeform_panY: 0,
+                        _freeform_scale: 1,
+                        layout_nativeDimEditable: true,
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        systemIcon: 'BsFillCollectionFill',
+                    },
                 },
             ],
             [
@@ -533,7 +545,7 @@ export namespace Docs {
                 DocumentType.AUDIO,
                 {
                     layout: { view: AudioBox, dataField: defaultDataKey },
-                    options: { _height: 100, layout_fitWidth: true, layout_forceReflow: true, nativeDimModifiable: true, systemIcon: 'BsFillVolumeUpFill' },
+                    options: { _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' },
                 },
             ],
             [
@@ -547,14 +559,14 @@ export namespace Docs {
                 DocumentType.PDF,
                 {
                     layout: { view: PDFBox, dataField: defaultDataKey },
-                    options: { _layout_curPage: 1, _layout_fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, systemIcon: 'BsFileEarmarkPdfFill' },
+                    options: { _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' },
                 },
             ],
             [
                 DocumentType.MAP,
                 {
                     layout: { view: MapBox, dataField: defaultDataKey },
-                    options: { map: '', _height: 600, _width: 800, nativeDimModifiable: true, systemIcon: 'BsFillPinMapFill' },
+                    options: { map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
                 },
             ],
             [
@@ -613,14 +625,25 @@ export namespace Docs {
                 DocumentType.EQUATION,
                 {
                     layout: { view: EquationBox, dataField: 'text' },
-                    options: { nativeDimModifiable: true, fontSize: '14px', layout_hideResizeHandles: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill' }, ///systemIcon: 'BsSuperscript' + BsSubscript
+                    options: {
+                        fontSize: '14px',
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        layout_nativeDimEditable: true,
+                        layout_hideDecorationTitle: true,
+                        systemIcon: 'BsCalculatorFill',
+                    }, ///systemIcon: 'BsSuperscript' + BsSubscript
                 },
             ],
             [
                 DocumentType.FUNCPLOT,
                 {
                     layout: { view: FunctionPlotBox, dataField: defaultDataKey },
-                    options: { nativeDimModifiable: true },
+                    options: {
+                        layout_reflowHorizontal: true,
+                        layout_reflowVertical: true,
+                        layout_nativeDimEditable: true,
+                    },
                 },
             ],
             [
@@ -672,12 +695,12 @@ export namespace Docs {
                     layout: { view: InkingStroke, dataField: 'stroke' },
                     options: {
                         systemIcon: 'BsFillPencilFill', //
-                        nativeDimModifiable: true,
-                        nativeHeightUnfrozen: true,
+                        layout_nativeDimEditable: true,
+                        layout_reflowVertical: true,
+                        layout_reflowHorizontal: true,
                         layout_hideDecorationTitle: true, // don't show title when selected
                         fitWidth: false,
                         layout_isSvg: true,
-                        layout_forceReflow: true,
                     },
                 },
             ],
@@ -685,7 +708,7 @@ export namespace Docs {
                 DocumentType.SCREENSHOT,
                 {
                     layout: { view: ScreenshotBox, dataField: defaultDataKey },
-                    options: { nativeDimModifiable: true, nativeHeightUnfrozen: true, systemIcon: 'BsCameraFill' },
+                    options: { layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' },
                 },
             ],
             [
@@ -693,7 +716,7 @@ export namespace Docs {
                 {
                     data: '',
                     layout: { view: ComparisonBox, dataField: defaultDataKey },
-                    options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', systemIcon: 'BsLayoutSplit' },
+                    options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
                 },
             ],
             [
@@ -714,14 +737,14 @@ export namespace Docs {
                 DocumentType.DATAVIZ,
                 {
                     layout: { view: DataVizBox, dataField: defaultDataKey },
-                    options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, nativeDimModifiable: true },
+                    options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true },
                 },
             ],
             [
                 DocumentType.LOADING,
                 {
                     layout: { view: LoadingBox, dataField: '' },
-                    options: { _layout_fitWidth: true, _fitHeight: true, nativeDimModifiable: true },
+                    options: { _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true },
                 },
             ],
             [
@@ -731,11 +754,9 @@ export namespace Docs {
                     layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 },
                     options: {
                         _height: 100,
-                        layout_forceReflow: true,
-                        nativeHeightUnfrozen: true,
                         mass1: '',
                         mass2: '',
-                        nativeDimModifiable: true,
+                        layout_nativeDimEditable: true,
                         position: '',
                         acceleration: '',
                         pendulum: '',
@@ -948,7 +969,7 @@ export namespace Docs {
 
         export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             const imgField = url instanceof ImageField ? url : new ImageField(url);
-            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { _nativeDimModifiable: false, _nativeHeightUnfrozen: false, title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function PresDocument(options: DocumentOptions = {}) {
@@ -1042,7 +1063,18 @@ export namespace Docs {
             return linkDoc;
         }
 
-        export function InkDocument(color: string, strokeWidth: number, stroke_bezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], isInkMask: boolean, options: DocumentOptions = {}) {
+        export function InkDocument(
+            points: PointData[],
+            options: DocumentOptions = {},
+            strokeWidth = ActiveInkWidth(),
+            color = ActiveInkColor(),
+            stroke_bezier = ActiveInkBezierApprox(),
+            fillColor = ActiveFillColor(),
+            arrowStart = ActiveArrowStart(),
+            arrowEnd = ActiveArrowEnd(),
+            dash = ActiveDash(),
+            isInkMask = ActiveIsInkMask()
+        ) {
             const ink = InstanceFromProto(Prototypes.get(DocumentType.INK), '', { title: 'ink', ...options });
             const I = Doc.GetProto(ink);
             // I.layout_hideOpenButton = true; // don't show open full screen button when selected
@@ -1253,6 +1285,40 @@ export namespace Docs {
 }
 
 export namespace DocUtils {
+    function matchFieldValue(doc: Doc, key: string, value: any): boolean {
+        const hasFunctionFilter = Utils.HasFunctionFilter(value);
+        if (hasFunctionFilter) {
+            return hasFunctionFilter(StrCast(doc[key]));
+        }
+        if (key === LinkedTo) {
+            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
+            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
+            const matchLink = (value: string, anchor: Doc) => {
+                const linkedToExp = value?.split('=');
+                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
+                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
+            };
+            // prettier-ignore
+            return  (value === Doc.FilterNone && !allLinks.length) || 
+                    (value === Doc.FilterAny && !!allLinks.length) ||
+                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
+                                           matchLink(value,DocCast(link.link_anchor_2)) ));
+        }
+        if (typeof value === 'string') {
+            value = value.replace(`,${Utils.noRecursionHack}`, '');
+        }
+        const fieldVal = doc[key];
+        // prettier-ignore
+        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
+            (value === Doc.FilterNone && fieldVal === undefined)) {
+            return true;
+        }
+        const vals = StrListCast(fieldVal); // list typing is very imperfect.  casting to a string list doesn't mean that the entries will actually be strings
+        if (vals.length) {
+            return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+        }
+        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+    }
     /**
      * @param docs
      * @param childFilters
@@ -1284,7 +1350,7 @@ export namespace DocUtils {
                   if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) {
                       return false;
                   }
-                  for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) {
+                  for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) {
                       const facet = filterFacets[facetKey];
 
                       // facets that match some value in the field of the document (e.g. some text field)
@@ -1303,8 +1369,8 @@ export namespace DocUtils {
                       const xs = Object.keys(facet).filter(value => facet[value] === 'x');
 
                       if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true;
-                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value));
-                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value));
+                      const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value));
+                      const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value));
                       const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length));
                       const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined);
                       const satisfiesMatchFacets = !matches.length
@@ -1492,7 +1558,7 @@ export namespace DocUtils {
             created = Docs.Create.AudioDocument(field.url.href, resolved);
             created.layout = AudioBox.LayoutString(fieldKey);
         } else if (field instanceof InkField) {
-            created = Docs.Create.InkDocument(ActiveInkColor(), ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved);
+            created = Docs.Create.InkDocument(field.inkData, resolved);
             created.layout = InkingStroke.LayoutString(fieldKey);
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
@@ -1802,7 +1868,7 @@ export namespace DocUtils {
                 proto.data_duration = result.duration;
             }
             if (overwriteDoc) {
-                Doc.removeCurrentlyLoading(overwriteDoc);
+                LoadingBox.removeCurrentlyLoading(overwriteDoc);
             }
             generatedDocuments.push(doc);
         }
@@ -1819,6 +1885,7 @@ export namespace DocUtils {
             _height: 35,
             x: x,
             y: y,
+            _layout_centered: BoolCast(Doc.UserDoc().layout_centered),
             _layout_fitWidth: true,
             _layout_autoHeight: true,
             _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards),
@@ -1844,7 +1911,7 @@ export namespace DocUtils {
                 if (overwriteDoc) {
                     overwriteDoc.isLoading = false;
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
@@ -1885,7 +1952,7 @@ export namespace DocUtils {
             if ((result as any).message) {
                 if (overwriteDoc) {
                     overwriteDoc.loadingError = (result as any).message;
-                    Doc.removeCurrentlyLoading(overwriteDoc);
+                    LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
             } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc);
         });
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index e789d8e20..2b94d35ee 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -1589,7 +1589,7 @@
             blockedPopoutsThrowError: true,
             closePopoutsOnUnload: true,
             showPopoutIcon: true,
-            showMaximiseIcon: true,
+            showMaximiseIcon: false,
             showCloseIcon: true,
             responsiveMode: 'onload', // Can be onload, always, or none.
             tabOverlapAllowance: 0, // maximum pixel overlap per tab
@@ -2622,7 +2622,7 @@
             /**
              * Maximise control - set the component to the full size of the layout
              */
-            if (this._getHeaderSetting('maximise')) {
+            if (false && this._getHeaderSetting('maximise')) {
                 maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent);
                 maximiseLabel = this._getHeaderSetting('maximise');
                 minimiseLabel = this._getHeaderSetting('minimise');
@@ -2640,7 +2640,7 @@
             /**
              * Close button
              */
-            if (this._isClosable()) {
+            if (false && this._isClosable()) {
                 closeStack = lm.utils.fnBind(this.parent.remove, this.parent);
                 label = this._getHeaderSetting('close');
                 this.closeButton = new lm.controls.HeaderButton(this, label, 'lm_close', closeStack);
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index f42336ee7..8a4f37121 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -73,7 +73,7 @@ export class CaptureManager extends React.Component<{}> {
                     <div
                         className="save"
                         onClick={() => {
-                            LightboxView.SetLightboxDoc(this._document);
+                            LightboxView.Instance.SetLightboxDoc(this._document);
                             this.close();
                         }}>
                         Save
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 87ee1b252..ac506e2d6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -339,7 +339,7 @@ export class CurrentUserUtils {
 
     /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents
     static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, toolTip: string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}, hidden?: boolean}[] {
-      const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; 
+      const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target?.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; 
       const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails";
         return [
             { title: "Dashboards",      toolTip: "Dashboards",      target: this.setupDashboards(doc, "myDashboards"),         ignoreClick: true, icon: "desktop",     funcs: {hidden: "IsNoviceMode()"} },
@@ -655,13 +655,13 @@ export class CurrentUserUtils {
             { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Vcenter",  toolTip: "Vertical center",      btnType: ButtonType.ToggleButton, icon: "pallet",      toolType:"vcent",    ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
             { title: "Align",    toolTip: "Alignment",            btnType: ButtonType.MultiToggleButton,                 toolType:"alignment", ignoreClick: true,
               subMenu: [
                 { title: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
                 { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
                 { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-              ]
-            },
+            ]},
             { title: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
             { title: "NoLink",   toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
@@ -793,11 +793,6 @@ export class CurrentUserUtils {
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
 
-    /// collection of documents rendered in the overlay layer above all tabs and other UI
-    static setupOverlays(doc: Doc, field = "myOverlayDocs") {
-        return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", isSystem: true });
-    }
-
     static setupPublished(doc:Doc, field = "myPublishedDocs") {
         return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
     }
@@ -901,7 +896,6 @@ export class CurrentUserUtils {
         this.setupSharedDocs(doc, sharingDocumentId);  // sets up the right sidebar collection for mobile upload documents and sharing
         this.setupDefaultIconTemplates(doc);  // creates a set of icon templates triggered by the document deoration icon
         this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
-        this.setupOverlays(doc);  // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard
         this.setupPublished(doc);  // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box)
         this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected
         this.setupTopbarButtons(doc);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7cc8afaa6..3c59a8060 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -13,6 +13,7 @@ import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { LoadingBox } from '../views/nodes/LoadingBox';
 import { PresBox } from '../views/nodes/trails';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
@@ -40,8 +41,8 @@ export class DocumentManager {
 
     //private constructor so no other class can create a nodemanager
     private constructor() {
-        if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = [];
-        observe(Doc.CurrentlyLoading, change => {
+        if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
+        observe(LoadingBox.CurrentlyLoading, change => {
             // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
             switch (change.type as any) {
                 case 'update':
@@ -306,7 +307,7 @@ export class DocumentManager {
             focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
             const { childDocView, viewSpec } = await iterator(docView);
             if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView, focused };
-            contextView = docView;
+            contextView = options.anchorDoc?.layout_unrendered ? childDocView : docView;
             docView = childDocView;
         }
     };
@@ -326,14 +327,15 @@ export class DocumentManager {
 
             if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.text_html) {
                 // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
-                contextView.htmlOverlayEffect = StrCast(options?.effect?.presentation_effect, StrCast(options?.effect?.followLinkAnimEffect));
+                contextView.htmlOverlayEffect = options.effect;
+                contextView.textHtmlOverlayTime = options.zoomTime;
                 contextView.textHtmlOverlay = StrCast(targetDoc.text_html);
                 DocumentManager._overlayViews.add(contextView);
             }
             Doc.AddUnHighlightWatcher(() => {
                 docView.rootDoc[Animation] = undefined;
                 DocumentManager.removeOverlayViews();
-                contextView && (contextView.htmlOverlayEffect = '');
+                contextView && (contextView.htmlOverlayEffect = undefined);
             });
         }
     }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 8d8975763..6e4de252d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -8,6 +8,7 @@ import { ScriptCast, StrCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import * as globalCssVariables from '../views/global/globalCssVariables.scss';
+import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
@@ -57,7 +58,7 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
 export namespace DragManager {
     let dragDiv: HTMLDivElement;
     let dragLabel: HTMLDivElement;
-    export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>;
+    export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
     export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
 
     export function Root() {
@@ -198,7 +199,7 @@ export namespace DragManager {
         };
         const finishDrag = async (e: DragCompleteEvent) => {
             const docDragData = e.docDragData;
-            setTimeout(() => dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding()));
+            setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
             onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -234,7 +235,7 @@ export namespace DragManager {
         };
         dragData.draggedDocuments.map(d => d.dragFactory); // does this help?  trying to make sure the dragFactory Doc is loaded
         StartDrag(eles, dragData, downX, downY, options, finishDrag);
-        dragData.draggedViews.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragStarting());
+        dragData.draggedViews.forEach(view => view.props.dragStarting?.());
         return true;
     }
 
@@ -360,11 +361,18 @@ export namespace DragManager {
         const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
         const dragElements = eles.map(ele => {
             // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
-            let useDim = false; // if doc is rotated by freeformview, then the dragged elements width and height won't reflect the unrotated dimensions, so we need to rely on the element knowing its own width/height.  \
+            // bcz: used to be false, but that made dragging collection w/ native dim's not work...
+            let useDim = true; // if doc is rotated by freeformview, then the dragged elements width and height won't reflect the unrotated dimensions, so we need to rely on the element knowing its own width/height.  \
             // if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu)
-            if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') {
-                ele = ele.parentElement.parentElement.parentElement;
-                rot.push(Number(ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0));
+            let rotation: number | undefined;
+            for (let parEle: HTMLElement | null | undefined = ele.parentElement; parEle; parEle = parEle?.parentElement) {
+                if (parEle.className === CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName) {
+                    rotation = (rotation ?? 0) + Number(parEle.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0);
+                }
+                parEle = parEle.parentElement;
+            }
+            if (rotation !== undefined) {
+                rot.push(rotation);
             } else {
                 useDim = true;
                 rot.push(0);
@@ -475,7 +483,7 @@ export namespace DragManager {
         };
 
         const cleanupDrag = action((undo: boolean) => {
-            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.CollectionFreeFormDocumentView?.().dragEnding());
+            (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
             hideDragShowOriginalElements(false);
             document.removeEventListener('pointermove', moveHandler, true);
             document.removeEventListener('pointerup', upHandler, true);
@@ -490,7 +498,7 @@ export namespace DragManager {
             if (dragData instanceof DocumentDragData) {
                 dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
             }
-            if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header' || e?.shiftKey) && dragData.draggedDocuments.length === 1) {
+            if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header') && dragData.draggedDocuments.length === 1) {
                 if (!startWindowDragTimer) {
                     startWindowDragTimer = setTimeout(async () => {
                         startWindowDragTimer = undefined;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index ba53a760f..608184596 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -197,14 +197,32 @@ export class LinkManager {
     }
 
     // finds the opposite anchor of a given anchor in a link
-    //TODO This should probably return undefined if there isn't an opposite anchor
-    //TODO This should also await the return value of the anchor so we don't filter out promises
     public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
-        const a1 = Cast(linkDoc.link_anchor_1, Doc, null);
-        const a2 = Cast(linkDoc.link_anchor_2, Doc, null);
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
-        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
-        if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+        const id = LinkManager.anchorIndex(linkDoc, anchor);
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        return id === '1' ? a2 : id === '2' ? a1 : id === '0' ? linkDoc : undefined;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return a2;
+        // if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return a1;
+        // if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
+    }
+    public static anchorIndex(linkDoc: Doc, anchor: Doc) {
+        const a1 = DocCast(linkDoc.link_anchor_1);
+        const a2 = DocCast(linkDoc.link_anchor_2);
+        if (linkDoc.link_matchEmbeddings) {
+            return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        }
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
+        if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
+        if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
+
+        // const a1 = DocCast(linkDoc.link_anchor_1);
+        // const a2 = DocCast(linkDoc.link_anchor_2);
+        // if (linkDoc.link_matchEmbeddings) {
+        //     return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
+        // }
+        // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
+        // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
     }
 }
 
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index d0f66d124..f7e6fa2dc 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,6 @@
-import { action, observable, ObservableMap } from 'mobx';
-import { computedFn } from 'mobx-utils';
+import { action, observable } from 'mobx';
 import { Doc, Opt } from '../../fields/Doc';
+import { DocViews } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
 import { listSpec } from '../../fields/Schema';
 import { Cast, DocCast } from '../../fields/Types';
@@ -12,9 +12,8 @@ import { UndoManager } from './UndoManager';
 
 export namespace SelectionManager {
     class Manager {
-        @observable IsDragging: boolean = false;
-        SelectedViewsMap: ObservableMap<DocumentView, Doc> = new ObservableMap();
         @observable SelectedViews: DocumentView[] = [];
+        @observable IsDragging: boolean = false;
         @observable SelectedSchemaDocument: Doc | undefined;
 
         @action
@@ -22,29 +21,18 @@ export namespace SelectionManager {
             manager.SelectedSchemaDocument = doc;
         }
         @action
-        SelectView(docView: DocumentView, ctrlPressed: boolean): void {
-            // if doc is not in SelectedDocuments, add it
-            if (!manager.SelectedViewsMap.get(docView)) {
-                if (!ctrlPressed) {
-                    this.DeselectAll();
-                }
-
+        SelectView(docView: DocumentView, extendSelection: boolean): void {
+            if (!docView.SELECTED) {
+                if (!extendSelection) this.DeselectAll();
                 manager.SelectedViews.push(docView);
-                manager.SelectedViewsMap.set(docView, docView.rootDoc);
+                docView.SELECTED = true;
                 docView.props.whenChildContentsActiveChanged(true);
-            } else if (!ctrlPressed && (Array.from(manager.SelectedViewsMap.entries()).length > 1 || manager.SelectedSchemaDocument)) {
-                Array.from(manager.SelectedViewsMap.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
-                manager.SelectedSchemaDocument = undefined;
-                manager.SelectedViews.length = 0;
-                manager.SelectedViewsMap.clear();
-                manager.SelectedViews.push(docView);
-                manager.SelectedViewsMap.set(docView, docView.rootDoc);
             }
         }
         @action
         DeselectView(docView?: DocumentView): void {
-            if (docView && manager.SelectedViewsMap.get(docView)) {
-                manager.SelectedViewsMap.delete(docView);
+            if (docView && manager.SelectedViews.includes(docView)) {
+                docView.SELECTED = false;
                 manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
                 docView.props.whenChildContentsActiveChanged(false);
             }
@@ -54,8 +42,10 @@ export namespace SelectionManager {
             LinkManager.currentLink = undefined;
             LinkManager.currentLinkAnchor = undefined;
             manager.SelectedSchemaDocument = undefined;
-            Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
-            manager.SelectedViewsMap.clear();
+            manager.SelectedViews.forEach(dv => {
+                dv.SELECTED = false;
+                dv.props.whenChildContentsActiveChanged(false);
+            });
             manager.SelectedViews.length = 0;
         }
     }
@@ -74,45 +64,24 @@ export namespace SelectionManager {
         manager.SelectSchemaViewDoc(document);
     }
 
-    const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {
-        // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
-        return manager.SelectedViewsMap.get(doc) ? true : false;
-    });
-    // computed functions, such as used in IsSelected generate errors if they're called outside of a
-    // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
-    // to avoid unnecessary mobx invalidations when running inside a reaction.
-    export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean {
-        return !doc
-            ? false
-            : outsideReaction
-            ? manager.SelectedViewsMap.get(doc)
-                ? true
-                : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
-            : IsSelectedCache(doc);
+    export function IsSelected(doc?: Doc): boolean {
+        return Array.from(doc?.[DocViews] ?? []).some(dv => dv?.SELECTED);
     }
 
     export function DeselectAll(except?: Doc): void {
-        let found: DocumentView | undefined = undefined;
-        if (except) {
-            for (const view of Array.from(manager.SelectedViewsMap.keys())) {
-                if (view.props.Document === except) found = view;
-            }
-        }
-
+        const found = manager.SelectedViews.find(dv => dv.Document === except);
         manager.DeselectAll();
         if (found) manager.SelectView(found, false);
     }
 
     export function Views(): Array<DocumentView> {
         return manager.SelectedViews;
-        // Array.from(manager.SelectedViewsMap.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._type_collection !== CollectionViewType.Docking);
     }
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
     export function Docs(): Doc[] {
         return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._type_collection !== CollectionViewType.Docking);
-        // Array.from(manager.SelectedViewsMap.values()).filter(doc => doc?._type_collection !== CollectionViewType.Docking);
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index f75322905..39c471970 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -12,7 +12,9 @@ import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Networking } from '../Network';
+import { GestureOverlay } from '../views/GestureOverlay';
 import { MainViewModal } from '../views/MainViewModal';
+import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
 import { GroupManager } from './GroupManager';
 import './SettingsManager.scss';
 import { undoBatch } from './UndoManager';
@@ -228,8 +230,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Show Button Labels'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())}
-                    toggleStatus={Doc.GetShowIconLabels()}
+                    onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
+                    toggleStatus={FontIconBox.ShowIconLabels}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
@@ -237,8 +239,8 @@ export class SettingsManager extends React.Component<{}> {
                     formLabel={'Recognize Ink Gestures'}
                     formLabelPlacement={'right'}
                     toggleType={ToggleType.SWITCH}
-                    onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())}
-                    toggleStatus={Doc.GetRecognizeGestures()}
+                    onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
+                    toggleStatus={GestureOverlay.RecognizeGestures}
                     size={Size.XSMALL}
                     color={SettingsManager.userColor}
                 />
@@ -448,7 +450,7 @@ export class SettingsManager extends React.Component<{}> {
                                     val: freeformScrollMode.Zoom,
                                 },
                             ]}
-                            selectedVal={StrCast(Doc.UserDoc().freeformScrollMode)}
+                            selectedVal={StrCast(Doc.UserDoc().freeformScrollMode, 'zoom')}
                             setSelectedVal={val => this.setFreeformScrollMode(val as string)}
                             dropdownType={DropdownType.SELECT}
                             type={Type.TERT}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index c0cd94067..fce43eef6 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -3,6 +3,8 @@ import { Doc } from '../../fields/Doc';
 
 export namespace SnappingManager {
     class Manager {
+        @observable ShiftKey = false;
+        @observable CtrlKey = false;
         @observable IsDragging: boolean = false;
         @observable IsResizing: Doc | undefined;
         @observable CanEmbed: boolean = false;
@@ -33,6 +35,12 @@ export namespace SnappingManager {
         return manager.vertSnapLines;
     }
 
+    export function SetShiftKey(down: boolean) {
+        runInAction(() => (manager.ShiftKey = down));
+    }
+    export function SetCtrlKey(down: boolean) {
+        runInAction(() => (manager.CtrlKey = down));
+    }
     export function SetIsDragging(dragging: boolean) {
         runInAction(() => (manager.IsDragging = dragging));
     }
@@ -42,6 +50,12 @@ export namespace SnappingManager {
     export function SetCanEmbed(canEmbed: boolean) {
         runInAction(() => (manager.CanEmbed = canEmbed));
     }
+    export function GetShiftKey() {
+        return manager.ShiftKey;
+    }
+    export function GetCtrlKey() {
+        return manager.CtrlKey;
+    }
     export function GetIsDragging() {
         return manager.IsDragging;
     }
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index e9170ec36..dca37c960 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -2,65 +2,105 @@ export class Transform {
     private _translateX: number = 0;
     private _translateY: number = 0;
     private _scale: number = 1;
+    private _rotate: number = 0;
 
     static Identity(): Transform {
         return new Transform(0, 0, 1);
     }
 
-    get TranslateX(): number { return this._translateX; }
-    get TranslateY(): number { return this._translateY; }
-    get Scale(): number { return this._scale; }
+    get TranslateX(): number {
+        return this._translateX;
+    }
+    get TranslateY(): number {
+        return this._translateY;
+    }
+    get Scale(): number {
+        return this._scale;
+    }
+    get Rotate(): number {
+        return this._rotate;
+    }
+    get RotateDeg(): number {
+        return (this._rotate * 180) / Math.PI;
+    }
 
-    constructor(x: number, y: number, scale: number) {
+    /**
+     * Represents a transformation/scale matrix (can contain a rotation value, but it is not used when transforming points)
+     * @param x
+     * @param y
+     * @param scale
+     * @param rotation NOTE: this is passed along but is NOT used by any of the transformation functionsStores
+     */
+    constructor(x: number, y: number, scale: number, rotationRadians?: number) {
         this._translateX = x;
         this._translateY = y;
         this._scale = scale;
+        this._rotate = rotationRadians ?? 0;
     }
 
+    /**
+     * Rotate in radians
+     * @param rot
+     * @returns the modified transformation
+     */
+    rotate = (rot: number): this => {
+        this._rotate += rot;
+        return this;
+    };
+    /**
+     * Rotation in degrees
+     * @param rot
+     * @returns the modified transformation
+     */
+    rotateDeg = (rot: number): this => {
+        this._rotate += (rot * Math.PI) / 180;
+        return this;
+    };
+
     translate = (x: number, y: number): this => {
         this._translateX += x;
         this._translateY += y;
         return this;
-    }
+    };
 
     scale = (scale: number): this => {
         this._scale *= scale;
         this._translateX *= scale;
         this._translateY *= scale;
         return this;
-    }
+    };
 
     scaleAbout = (scale: number, x: number, y: number): this => {
         this._translateX += x * this._scale - x * this._scale * scale;
         this._translateY += y * this._scale - y * this._scale * scale;
         this._scale *= scale;
         return this;
-    }
+    };
 
     transform = (transform: Transform): this => {
         this._translateX = transform._translateX + transform._scale * this._translateX;
         this._translateY = transform._translateY + transform._scale * this._translateY;
         this._scale *= transform._scale;
         return this;
-    }
+    };
 
     preTranslate = (x: number, y: number): this => {
         this._translateX += this._scale * x;
         this._translateY += this._scale * y;
         return this;
-    }
+    };
 
     preScale = (scale: number): this => {
         this._scale *= scale;
         return this;
-    }
+    };
 
     preTransform = (transform: Transform): this => {
         this._translateX += transform._translateX * this._scale;
         this._translateY += transform._translateY * this._scale;
         this._scale *= transform._scale;
         return this;
-    }
+    };
 
     translated = (x: number, y: number): Transform => this.copy().translate(x, y);
 
@@ -82,18 +122,17 @@ export class Transform {
         y *= this._scale;
         y += this._translateY;
         return [x, y];
-    }
+    };
 
     transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
 
-    transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
+    transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
         [x, y] = this.transformPoint(x, y);
         [width, height] = this.transformDirection(width, height);
         return { x, y, width, height };
     }
 
-    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
-
-    copy = () => new Transform(this._translateX, this._translateY, this._scale);
+    inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
 
-}
\ No newline at end of file
+    copy = () => new Transform(this._translateX, this._translateY, this._scale, this._rotate);
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 014a6358f..9e2ed7822 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -12,6 +12,7 @@ import { PrefetchProxy } from '../../fields/Proxy';
 import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ImageCast, StrCast } from '../../fields/Types';
+import { normalizeEmail } from '../../fields/util';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
 import { HistoryUtil } from '../util/History';
@@ -149,7 +150,7 @@ export class DashboardView extends React.Component {
                             : this.getDashboards(this.selectedDashboardGroup).map(dashboard => {
                                   const href = ImageCast(dashboard.thumb)?.url?.href;
                                   const shared = Object.keys(dashboard[DocAcl])
-                                      .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key))
+                                      .filter(key => key !== `acl-${normalizeEmail(Doc.CurrentUserEmail)}` && !['acl-Me', 'acl-Guest'].includes(key))
                                       .some(key => dashboard[DocAcl][key] !== AclPrivate);
                                   return (
                                       <div
@@ -372,6 +373,8 @@ export class DashboardView extends React.Component {
         Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
         dashboardDoc['pane-count'] = 1;
         freeformDoc.embedContainer = dashboardDoc;
+        dashboardDoc.myOverlayDocs = new List<Doc>();
+        dashboardDoc.myPublishedDocs = new List<Doc>();
 
         Doc.AddDocToList(Doc.MyDashboards, 'data', dashboardDoc);
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 483b92957..9ff27d9a0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,16 +1,15 @@
 import { action, computed, observable } from 'mobx';
 import { DateField } from '../../fields/DateField';
-import { Doc, DocListCast, HierarchyMapping, Opt, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocAcl, DocData } from '../../fields/DocSymbols';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
-import { Cast, DocCast, StrCast } from '../../fields/Types';
-import { distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
+import { Cast } from '../../fields/Types';
+import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util';
 import { returnFalse } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
-import { InteractionUtils } from '../util/InteractionUtils';
 import { DocumentView } from './nodes/DocumentView';
-import { Touchable } from './Touchable';
+import * as React from 'react';
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 export interface DocComponentProps {
@@ -20,7 +19,7 @@ export interface DocComponentProps {
     LayoutTemplateString?: string;
 }
 export function DocComponent<P extends DocComponentProps>() {
-    class Component extends Touchable<P> {
+    class Component extends React.Component<React.PropsWithChildren<P>> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
             return this.props.Document;
@@ -41,8 +40,6 @@ export function DocComponent<P extends DocComponentProps>() {
         @computed get fieldKey() {
             return this.props.fieldKey;
         }
-
-        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -53,13 +50,13 @@ interface ViewBoxBaseProps {
     DataDoc?: Doc;
     DocumentView?: () => DocumentView;
     fieldKey: string;
-    isSelected: (outsideReaction?: boolean) => boolean;
+    isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
     renderDepth: number;
-    rootSelected: (outsideReaction?: boolean) => boolean;
+    rootSelected: () => boolean;
 }
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
-    class Component extends Touchable<P> {
+    class Component extends React.Component<React.PropsWithChildren<P>> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         //@computed get Document(): T { return schemaCtor(this.props.Document); }
 
@@ -79,8 +76,6 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
         @computed get fieldKey() {
             return this.props.fieldKey;
         }
-
-        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -94,13 +89,13 @@ export interface ViewBoxAnnotatableProps {
     isContentActive: () => boolean | undefined;
     select: (isCtrlPressed: boolean) => void;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
-    isSelected: (outsideReaction?: boolean) => boolean;
-    rootSelected: (outsideReaction?: boolean) => boolean;
+    isSelected: () => boolean;
+    rootSelected: () => boolean;
     renderDepth: number;
     isAnnotationOverlay?: boolean;
 }
 export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
-    class Component extends Touchable<P> {
+    class Component extends React.Component<React.PropsWithChildren<P>> {
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@@ -127,8 +122,6 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
 
-        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
         @computed public get annotationKey() {
             return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : '');
         }
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index f41cf1385..bbd951481 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -4,7 +4,6 @@ $linkGap: 3px;
 $headerHeight: 20px;
 $resizeHandler: 8px;
 
-.documentDecorations-Dark,
 .documentDecorations {
     position: absolute;
     z-index: 2000;
@@ -40,14 +39,12 @@ $resizeHandler: 8px;
         border-radius: 50%;
     }
 }
-.documentDecorations-Dark {
-    background: dimgray;
-}
 
 .documentDecorations-container {
     position: absolute;
     top: 0;
     left: 0;
+    transform-origin: 50% calc(50% + 10px);
     display: grid;
     grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler;
     grid-template-columns: $resizeHandler 1fr $resizeHandler;
@@ -60,6 +57,7 @@ $resizeHandler: 8px;
         flex-direction: row;
         gap: 2px;
         pointer-events: all;
+        color: black;
         cursor: move;
 
         .documentDecorations-openButton {
@@ -169,7 +167,6 @@ $resizeHandler: 8px;
             }
         }
 
-        .documentDecorations-title-Dark,
         .documentDecorations-title {
             opacity: 1;
             width: calc(100% - 60px); // = margin-left + margin-right
@@ -188,22 +185,13 @@ $resizeHandler: 8px;
                 opacity: 1;
             }
 
-            .documentDecorations-titleSpan,
-            .documentDecorations-titleSpan-Dark {
+            .documentDecorations-titleSpan {
                 width: 100%;
                 border-radius: 8px;
                 background: $light-gray;
                 display: inline-block;
                 cursor: move;
             }
-            .documentDecorations-titleSpan-Dark {
-                background: hsla(0, 0%, 0%, 0.412);
-            }
-        }
-
-        .documentDecorations-title-Dark {
-            color: white;
-            background: black;
         }
 
         .documentDecorations-titleBackground {
@@ -326,11 +314,6 @@ $resizeHandler: 8px;
         }
     }
 
-    .documentDecorations-resizer-Dark {
-        background: $light-gray;
-        opacity: 0.2;
-    }
-
     .documentDecorations-topLeftResizer,
     .documentDecorations-leftResizer,
     .documentDecorations-bottomLeftResizer {
@@ -357,6 +340,7 @@ $resizeHandler: 8px;
         background: $medium-gray;
         height: 10;
         width: 10;
+        opacity: 0.5;
         pointer-events: all;
         cursor: nwse-resize;
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5a145e94a..d4b474de9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,19 +1,18 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { IconButton } from 'browndash-components';
-import { action, computed, observable, reaction } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Field, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, DocData } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
@@ -46,70 +45,64 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     private _linkBoxHeight = 20 + 3; // link button height + margin
     private _titleHeight = 20;
     private _resizeUndo?: UndoManager.Batch;
-    private _offX = 0;
-    private _offY = 0; // offset from click pt to inner edge of resize border
-    private _snapX = 0;
-    private _snapY = 0; // last snapped location of resize border
-    private _dragHeights = new Map<Doc, { start: number; lowest: number }>();
+    private _offset = { x: 0, y: 0 }; // offset from click pt to inner edge of resize border
+    private _snapPt = { x: 0, y: 0 }; // last snapped location of resize border
     private _inkDragDocs: { doc: Doc; x: number; y: number; width: number; height: number }[] = [];
+    private _interactionLock?: boolean;
 
+    @observable _showNothing = true;
     @observable private _accumulatedTitle = '';
     @observable private _titleControlString: string = '#title';
     @observable private _editingTitle = false;
     @observable private _hidden = false;
-    @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set.
-    @observable public pushIcon: IconProp = 'arrow-alt-circle-up';
-    @observable public pullIcon: IconProp = 'arrow-alt-circle-down';
-    @observable public pullColor: string = 'white';
     @observable private _isRotating: boolean = false;
     @observable private _isRounding: boolean = false;
-    @observable private showLayoutAcl: boolean = false;
+    @observable private _showLayoutAcl: boolean = false;
+    @observable private _showRotCenter = false; // whether to show a draggable green dot that represents the center of rotation
+    @observable private _rotCenter = [0, 0]; // the center of rotation in object coordinates (0,0) = object center (not top left!)
 
     constructor(props: any) {
         super(props);
         DocumentDecorations.Instance = this;
-        reaction(
-            () => SelectionManager.Views().slice(),
-            action(views => {
-                this._showNothing = !DocumentView.LongPress && views.length === 1; // show decorations if multiple docs are selected or we're long pressing
-                this._editingTitle = false;
-            })
-        );
-        document.addEventListener(
-            // show decorations whenever pointer moves outside of selection bounds.
-            'pointermove',
+        document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
             action(e => {
-                if (this.Bounds.x || this.Bounds.y || this.Bounds.r || this.Bounds.b) {
-                    if (this.Bounds.x !== Number.MAX_VALUE && (this.Bounds.x > e.clientX + 10 || this.Bounds.r < e.clientX - 10 || this.Bounds.y > e.clientY + 10 || this.Bounds.b < e.clientY - 10)) {
-                        this._showNothing = false;
-                    } else {
-                        this._showNothing = true;
-                    }
-                }
-            })
-        );
+                const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
+                const {x,y} = Utils.rotPt(e.clientX - center.x, 
+                                          e.clientY - center.y, 
+                                          NumCast(SelectionManager.Views().lastElement()?.screenToLocalTransform().Rotate));
+                (this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
+                    (this.Bounds.x > center.x+x + this._resizeBorderWidth / 2 || 
+                     this.Bounds.r < center.x+x - this._resizeBorderWidth / 2 || 
+                     this.Bounds.y > center.y+y + this._resizeBorderWidth / 2 || 
+                     this.Bounds.b < center.y+y - this._resizeBorderWidth / 2)));
+            })); // prettier-ignore
     }
 
-    @computed
-    get Bounds() {
-        if (LinkFollower.IsFollowing || DocumentView.ExploreMode) return { x: 0, y: 0, r: 0, b: 0 };
-        const views = SelectionManager.Views();
-        return views
-            .filter(dv => dv.props.renderDepth > 0)
-            .map(dv => dv.getBounds())
-            .reduce(
-                (bounds, rect) =>
-                    !rect
-                        ? bounds
-                        : {
-                              x: Math.min(rect.left, bounds.x),
-                              y: Math.min(rect.top, bounds.y),
-                              r: Math.max(rect.right, bounds.r),
-                              b: Math.max(rect.bottom, bounds.b),
-                              c: views.length === 1 ? rect.center : undefined,
-                          },
-                { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }
-            );
+    @computed get ClippedBounds() {
+        const bounds = this.Bounds;
+        const leftBounds = this.props.boundsLeft;
+        const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
+        bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
+        bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
+        const borderRadiusDraggerWidth = 15;
+        bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
+        bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
+        return bounds;
+    }
+
+    @computed get Bounds() {
+        return (LinkFollower.IsFollowing || DocumentView.ExploreMode) ? 
+            { x: 0, y: 0, r: 0, b: 0 }
+            : SelectionManager.Views()
+                .filter(dv => dv.props.renderDepth > 0)
+                .map(dv => dv.getBounds())
+                .reduce((bounds, rect) => !rect ? bounds
+                            : { x: Math.min(rect.left, bounds.x),
+                                y: Math.min(rect.top, bounds.y),
+                                r: Math.max(rect.right, bounds.r),
+                                b: Math.max(rect.bottom, bounds.b),
+                                c: SelectionManager.Views().length === 1 ? rect.center : undefined },
+                    { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }); // prettier-ignore
     }
 
     @action
@@ -126,10 +119,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
                             if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.RemFromMyPublished(d.rootDoc);
                             }
                             if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc);
+                                Doc.AddToMyPublished(d.rootDoc);
                             }
                         }
                         //@ts-ignore
@@ -165,48 +158,43 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         }
     };
 
-    @action onContainerDown = (e: React.PointerEvent): void => {
-        const first = SelectionManager.Views()[0];
-        const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+    onContainerDown = (e: React.PointerEvent) => {
+        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
         if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
-            setupMoveUpEvents(
-                this,
-                e,
-                e => this.onBackgroundMove(true, e),
-                e => {},
-                emptyFunction
-            );
+            setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
+            e.stopPropagation();
         }
     };
 
-    @action onTitleDown = (e: React.PointerEvent): void => {
-        const first = SelectionManager.Views()[0];
-        const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+    onTitleDown = (e: React.PointerEvent) => {
+        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
         if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
             setupMoveUpEvents(
                 this,
                 e,
                 e => this.onBackgroundMove(true, e),
-                e => {},
+                emptyFunction,
                 action(e => {
                     !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString);
                     this._editingTitle = true;
                     this._keyinput.current && setTimeout(this._keyinput.current.focus);
                 })
             );
+            e.stopPropagation();
         }
     };
 
-    onBackgroundDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
-
+    onBackgroundDown = (e: React.PointerEvent) => {
+        setupMoveUpEvents(this, e, e => this.onBackgroundMove(false, e), emptyFunction, emptyFunction);
+        e.stopPropagation();
+    };
     @action
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
-        const first = SelectionManager.Views()[0];
-        const effectiveLayoutAcl = GetEffectiveAcl(first.rootDoc);
+        const dragDocView = SelectionManager.Views()[0];
+        const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.rootDoc);
         if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
             return false;
         }
-        const dragDocView = SelectionManager.Views()[0];
         const containers = new Set<Doc | undefined>();
         SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
         if (containers.size > 1) return false;
@@ -237,9 +225,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     _deleteAfterIconify = false;
     _iconifyBatch: UndoManager.Batch | undefined;
     onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
-        const views = SelectionManager.Views()
-            .slice()
-            .filter(v => v && v.props.renderDepth > 0);
+        const views = SelectionManager.Views().filter(v => v && v.props.renderDepth > 0);
         if (forceDeleteOrIconify === false && this._iconifyBatch) return;
         this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
         var iconifyingCount = views.length;
@@ -254,7 +240,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             iconView.props.removeDocument?.(iconView.props.Document);
                         }
                     });
-                    views.forEach(v => SelectionManager.DeselectView());
+                    views.forEach(SelectionManager.DeselectView);
                 }
                 this._iconifyBatch?.end();
                 this._iconifyBatch = undefined;
@@ -270,21 +256,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (forceDeleteOrIconify) finished(forceDeleteOrIconify);
         else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
     };
+
     onMaximizeDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(
-            this,
-            e,
-            () => {
-                DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]);
-                return true;
-            },
-            emptyFunction,
-            this.onMaximizeClick,
-            false,
-            false
-        );
+        setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views().lastElement().rootDoc]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
+        e.stopPropagation();
     };
-
     onMaximizeClick = (e: any): void => {
         const selectedDocs = SelectionManager.Views();
         if (selectedDocs.length) {
@@ -295,8 +271,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 // open centered in a new workspace with Shift Key
                 const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
                 embedding.embedContainer = undefined;
-                embedding.x = -embedding[Width]() / 2;
-                embedding.y = -embedding[Height]() / 2;
+                embedding.x = -NumCast(embedding._width) / 2;
+                embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
@@ -307,7 +283,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
                     Doc.deiconifyView(openDoc);
                 }
-                LightboxView.SetLightboxDoc(
+                LightboxView.Instance.SetLightboxDoc(
                     openDoc,
                     undefined,
                     selectedDocs.slice(1).map(view => view.rootDoc)
@@ -322,63 +298,51 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         SelectionManager.DeselectAll();
     };
 
-    onSelectorClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
+    onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
     /**
-     * Handles setting up events when user clicks on the border radius editor
-     * @param e PointerEvent
+     * sets up events when user clicks on the border radius editor
      */
     @action
     onRadiusDown = (e: React.PointerEvent): void => {
         this._isRounding = DocumentView.Interacting = true;
         this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
-        // Call util move event function
         setupMoveUpEvents(
-            this, // target
-            e, // pointerEvent
-            (e, down) => {
-                const x = this.Bounds.x + 3;
-                const y = this.Bounds.y + 3;
+            this,
+            e,
+            e => {
+                const [x, y] = [this.Bounds.x + 3, this.Bounds.y + 3];
                 const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
-                let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
-                if (e.clientX < x && e.clientY < y) dist = 0;
-                SelectionManager.Views()
-                    .map(dv => dv.props.Document)
-                    .map(doc => {
-                        const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
-                        const ratio = dist / maxDist;
-                        const radius = Math.min(1, ratio) * docMax;
-                        doc.layout_borderRounding = `${radius}px`;
-                    });
+                const dist = e.clientX < x && e.clientY < y ? 0 : Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y));
+                SelectionManager.Docs().map(doc => {
+                    const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2);
+                    const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box
+                    doc.layout_borderRounding = `${radius}px`;
+                });
                 return false;
-            }, // moveEvent
+            },
             action(e => {
                 DocumentView.Interacting = this._isRounding = false;
                 this._resizeUndo?.end();
             }), // upEvent
-            e => {}, // clickEvent,
+            emptyFunction,
             true
         );
+        e.stopPropagation();
     };
 
     @action
     onLockDown = (e: React.PointerEvent): void => {
-        // Call util move event function
         setupMoveUpEvents(
-            this, // target
-            e, // pointerEvent
-            returnFalse, // moveEvent
-            emptyFunction, // upEvent
-            e => {
-                UndoManager.RunInBatch(
-                    () =>
-                        SelectionManager.Views().map(dv => {
-                            dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition;
-                            dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined;
-                        }),
-                    'toggleBackground'
-                );
-            } // clickEvent
+            this,
+            e,
+            returnFalse, // don't care about move or up event,
+            emptyFunction, // just care about whether we get a click event
+            e => UndoManager.RunInBatch(
+                () => SelectionManager.Docs().forEach(doc =>
+                        doc._pointerEvents = (doc._lockedPosition = !doc._lockedPosition)? 'none' : undefined ),
+                'toggleBackground' ) // prettier-ignore
         );
+        e.stopPropagation();
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
@@ -396,16 +360,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         setupMoveUpEvents(
             this,
             e,
-            action((e: PointerEvent, down: number[], delta: number[]) => {
-                this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]);
-                return false;
-            }), // moveEvent
-            action(action(() => (this._isRotating = false))), // upEvent
-            action((e, doubleTap) => {
-                seldocview.rootDoc.rotation_centerX = 0;
-                seldocview.rootDoc.rotation_centerY = 0;
-            })
-        );
+            (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
+                this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean, 
+            action(e => (this._isRotating = false)), // upEvent
+            action(e => (seldocview.rootDoc.rotation_centerX = seldocview.rootDoc.rotation_centerY = 0))
+        ); // prettier-ignore
     };
 
     @action
@@ -476,240 +435,175 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     onPointerDown = (e: React.PointerEvent): void => {
         SnappingManager.SetIsResizing(SelectionManager.Docs().lastElement());
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+        e.stopPropagation();
         DocumentView.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         this._resizeHdlId = e.currentTarget.className;
         const bounds = e.currentTarget.getBoundingClientRect();
-        this._offX = this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX;
-        this._offY = this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY;
+        this._offset = { x: this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX, y: this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY };
         this._resizeUndo = UndoManager.StartBatch('drag resizing');
-        this._snapX = e.pageX;
-        this._snapY = e.pageY;
-        const ffviewSet = new Set<CollectionFreeFormView>();
-        SelectionManager.Views().forEach(docView => {
-            const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-            ffview && ffviewSet.add(ffview);
-            this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) });
-        });
-        Array.from(ffviewSet).map(ffview => ffview.dragStarting(false, false));
+        this._snapPt = { x: e.pageX, y: e.pageY };
+        SelectionManager.Views().forEach(docView => docView.CollectionFreeFormView?.dragStarting(false, false));
     };
 
+    projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => {
+        // need to generalize for bl and tr drag handles
+        const project = (p: number[], a: number[], b: number[]) => {
+            const atob = [b[0] - a[0], b[1] - a[1]];
+            const atop = [p[0] - a[0], p[1] - a[1]];
+            const len = atob[0] * atob[0] + atob[1] * atob[1];
+            let dot = atop[0] * atob[0] + atop[1] * atob[1];
+            const t = dot / len;
+            dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
+            return [a[0] + atob[0] * t, a[1] + atob[1] * t];
+        };
+        const tl = docView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
+    };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
         const first = SelectionManager.Views()[0];
         const effectiveAcl = GetEffectiveAcl(first.rootDoc);
         if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
         if (!first) return false;
-        let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
         var fixedAspect = Doc.NativeAspect(first.layoutDoc);
+        const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', '');
+
+        const thisPt = // do snapping of drag point
+            fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft')
+                ? DragManager.snapDragAspect(this.projectDragToAspect(e, first, fixedAspect), fixedAspect)
+                : DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
+
+        const { scale, refPt } = this.getResizeVals(thisPt, dragHdl);
+
+        !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
+            this._interactionLock = true;
+            this._snapPt = thisPt;
+            e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.props.Document) && docView.toggleNativeDimensions()));
+            const fixedAspect = SelectionManager.Docs().some(this.hasFixedAspect);
+            const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y};
+            SelectionManager.Views().forEach(docView => 
+                this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore
+            await new Promise<any>(res => setTimeout(() => res(this._interactionLock = undefined)));
+        }); // prettier-ignore
+
+        return false;
+    };
 
-        const resizeHdl = this._resizeHdlId.split(' ')[0];
-        if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) {
-            // need to generalize for bl and tr drag handles
-            const project = (p: number[], a: number[], b: number[]) => {
-                const atob = [b[0] - a[0], b[1] - a[1]];
-                const atop = [p[0] - a[0], p[1] - a[1]];
-                const len = atob[0] * atob[0] + atob[1] * atob[1];
-                let dot = atop[0] * atob[0] + atop[1] * atob[1];
-                const t = dot / len;
-                dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
-                return [a[0] + atob[0] * t, a[1] + atob[1] * t];
-            };
-            const tl = first.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            const drag = project([e.clientX + this._offX, e.clientY + this._offY], tl, [tl[0] + fixedAspect, tl[1] + 1]);
-            thisPt = DragManager.snapDragAspect(drag, fixedAspect);
+    //
+    // determines how much to resize, and determines the resize reference point
+    //
+    getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => {
+        const [w, h] = [this.Bounds.r - this.Bounds.x, this.Bounds.b - this.Bounds.y];
+        const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y];
+        switch (dragHdl) {
+            case 'topLeft':    return { scale: { x: 1 - moveX / w, y: 1 -moveY / h },  refPt: [this.Bounds.r, this.Bounds.b] };
+            case 'topRight':   return { scale: { x: 1 + moveX / w, y: 1 -moveY / h },  refPt: [this.Bounds.x, this.Bounds.b] };
+            case 'top':        return { scale: { x: 1,             y: 1 -moveY / h },  refPt: [this.Bounds.x, this.Bounds.b] };
+            case 'left':       return { scale: { x: 1 - moveX / w, y: 1 },             refPt: [this.Bounds.r, this.Bounds.y] };
+            case 'bottomLeft': return { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.Bounds.r, this.Bounds.y] };
+            case 'right':      return { scale: { x: 1 + moveX / w, y: 1 },             refPt: [this.Bounds.x, this.Bounds.y] };
+            case 'bottomRight':return { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.Bounds.x, this.Bounds.y] };
+            case 'bottom':     return { scale: { x: 1,             y: 1 + moveY / h }, refPt: [this.Bounds.x, this.Bounds.y] };
+            default:           return { scale: { x: 1,             y: 1 },             refPt: [this.Bounds.x, this.Bounds.y] };
+        } // prettier-ignore
+    };
+
+    //
+    // determines if anything being dragged directly or via a group has a fixed aspect ratio (in which case we resize uniformly)
+    //
+    hasFixedAspect = (doc: Doc): boolean => (doc.isGroup ? DocListCast(doc.data).some(this.hasFixedAspect) : !BoolCast(doc.layout_nativeDimEditable));
+
+    //
+    // resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
+    //
+    resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => {
+        const doc = docView.rootDoc;
+        if (doc.isGroup) {
+            DocListCast(doc.data)
+                .map(member => DocumentManager.Instance.getDocumentView(member, docView)!)
+                .forEach(member => this.resizeView(member, refPt, scale, opts));
+            doc.xPadding = NumCast(doc.xPadding) * scale.x;
+            doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            thisPt = DragManager.snapDrag(e, -this._offX, -this._offY, this._offX, this._offY);
-        }
+            const refCent = docView.props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
+            const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
+
+            const modifyNativeDim =
+                (opts.ctrlKey && doc.layout_nativeDimEditable) || // e.g., PDF or web page
+                (doc.layout_reflowHorizontal && opts.dragHdl !== 'bottom' && opts.dragHdl !== 'top') || // eg rtf or some web pages
+                (doc.layout_reflowVertical && (opts.dragHdl === 'bottom' || opts.dragHdl === 'top' || opts.ctrlKey)); // eg rtf, web, pdf
+            if (nwidth && nheight && !modifyNativeDim) {
+                // eg., dragging right resizer on PDF -- enforce native dimensions because not expliclty overridden with ctrl or bottom resize drag
+                scale.x === 1 ? (scale.x = scale.y) : (scale.y = scale.x);
+            }
 
-        move[0] = thisPt.x - this._snapX;
-        move[1] = thisPt.y - this._snapY;
-        this._snapX = thisPt.x;
-        this._snapY = thisPt.y;
-        let dragBottom = false,
-            dragRight = false,
-            dragBotRight = false,
-            dragTop = false;
-        let dXin = 0,
-            dYin = 0,
-            dWin = 0,
-            dHin = 0;
-        switch (this._resizeHdlId.split(' ')[0]) {
-            case '':
-                break;
-            case 'documentDecorations-topLeftResizer':
-                dXin = -1;
-                dYin = -1;
-                dWin = -move[0];
-                dHin = -move[1];
-                break;
-            case 'documentDecorations-topRightResizer':
-                dWin = move[0];
-                dYin = -1;
-                dHin = -move[1];
-                break;
-            case 'documentDecorations-topResizer':
-                dYin = -1;
-                dHin = -move[1];
-                dragTop = true;
-                break;
-            case 'documentDecorations-bottomLeftResizer':
-                dXin = -1;
-                dWin = -move[0];
-                dHin = move[1];
-                break;
-            case 'documentDecorations-bottomRightResizer':
-                dWin = move[0];
-                dHin = move[1];
-                dragBotRight = true;
-                break;
-            case 'documentDecorations-bottomResizer':
-                dHin = move[1];
-                dragBottom = true;
-                break;
-            case 'documentDecorations-leftResizer':
-                dXin = -1;
-                dWin = -move[0];
-                break;
-            case 'documentDecorations-rightResizer':
-                dWin = move[0];
-                dragRight = true;
-                break;
+            if (['right', 'left'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) {
+                const setData = Doc.NativeWidth(Doc.GetProto(doc)) === doc.nativeWidth;
+                doc.nativeWidth = scale.x * Doc.NativeWidth(doc);
+                if (setData) Doc.SetNativeWidth(Doc.GetProto(doc), NumCast(doc.nativeWidth));
+                if (doc.layout_reflowVertical && !NumCast(doc.nativeHeight)) {
+                    doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF
+                }
+            }
+            if (['bottom', 'top'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) {
+                const setData = Doc.NativeHeight(Doc.GetProto(doc)) === doc.nativeHeight;
+                doc._nativeHeight = scale.y * Doc.NativeHeight(doc);
+                if (setData) Doc.SetNativeHeight(Doc.GetProto(doc), NumCast(doc._nativeHeight));
+            }
+
+            doc._width = NumCast(doc._width) * scale.x;
+            doc._height = NumCast(doc._height) * scale.y;
+            const { deltaX, deltaY } = this.realignRefPt(doc, refCent, initWidth, initHeight);
+            doc.x = NumCast(doc.x) + deltaX;
+            doc.y = NumCast(doc.y) + deltaY;
+
+            doc._layout_modificationDate = new DateField();
+            scale.y !== 1 && (doc._layout_autoHeight = undefined);
         }
+    };
 
-        const isGroup = first.rootDoc._isGroup ? first.rootDoc : undefined;
-        const scaleViews = isGroup ? DocListCast(isGroup.data).map(doc => DocumentManager.Instance.getFirstDocumentView(doc)!) : SelectionManager.Views();
-        const aggBounds = aggregateBounds(scaleViews.map(view => view.rootDoc) as any, 0, 0);
-        const refWidth = aggBounds.r - aggBounds.x;
-        const refHeight = aggBounds.b - aggBounds.y;
-        const scaleRefPt = first.props
-            .ScreenToLocalTransform()
-            .inverse()
-            .transformPoint(
-                NumCast(isGroup?._xPadding) + (dXin ? refWidth : 0), //
-                NumCast(isGroup?._yPadding) + (dYin ? refHeight : 0)
-            );
-        scaleViews.forEach(
-            action((docView: DocumentView) => {
-                if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
-                if (dXin !== 0 || dYin !== 0 || dWin !== 0 || dHin !== 0) {
-                    const doc = docView.rootDoc;
-                    const refCent = docView.props.ScreenToLocalTransform().transformPoint(scaleRefPt[0], scaleRefPt[1]);
-
-                    if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight) && doc._nativeWidth !== undefined) {
-                        doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth;
-                    }
-                    const nwidth = docView.nativeWidth;
-                    const nheight = docView.nativeHeight;
-                    const docwidth = NumCast(doc._width);
-                    let docheight = (hgt => (!hgt || isNaN(hgt) ? 20 : hgt))(NumCast(doc._height) || (nheight / nwidth) * docwidth);
-                    let dW = docwidth * (dWin / refWidth);
-                    let dH = docheight * (dHin / refHeight);
-                    const scale = docView.props.ScreenToLocalTransform().Scale;
-                    const modifyNativeDim = (e.ctrlKey && doc.nativeDimModifiable) || (doc.layout_forceReflow && !dragBottom && !dragTop) || (doc.nativeHeightUnfrozen && (dragBottom || dragTop || e.ctrlKey));
-                    if (nwidth && nheight) {
-                        if (nwidth / nheight !== docwidth / docheight && !dragBottom && !dragTop) {
-                            docheight = (nheight / nwidth) * docwidth;
-                        }
-                        if (modifyNativeDim && !dragBottom && !dragTop) {
-                            // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
-                            if (Math.abs(dW) > Math.abs(dH)) dH = (dW * nheight) / nwidth;
-                            else dW = (dH * nwidth) / nheight;
-                        }
-                    }
-                    let actualdW = Math.max(docwidth + dW * scale, 20);
-                    let actualdH = Math.max(docheight + dH * scale, 20);
-                    let dX = !dWin ? 0 : (scale * refCent[0] * -dWin) / refWidth;
-                    let dY = !dHin ? 0 : (scale * refCent[1] * -dHin) / refHeight;
-                    const preserveNativeDim = !doc._nativeHeightUnfrozen && !doc._nativeDimModifiable;
-                    const fixedAspect = nwidth && nheight && (!doc._layout_fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
-                    if (fixedAspect) {
-                        if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
-                            if (dragRight && modifyNativeDim) {
-                                if (Doc.NativeWidth(doc)) {
-                                    doc._nativeWidth = (actualdW / (docwidth || 1)) * Doc.NativeWidth(doc);
-                                }
-                            } else {
-                                if (!doc._layout_fitWidth || preserveNativeDim) {
-                                    actualdH = (nheight / nwidth) * actualdW;
-                                    dYin && (dY = -dW * scale * (nheight / nwidth));
-                                    doc._height = actualdH;
-                                } else if (!modifyNativeDim || dragBotRight) {
-                                    doc._height = actualdH;
-                                }
-                            }
-                            doc._width = actualdW;
-                        } else {
-                            if ((dragBottom || dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._layout_fitWidth))) {
-                                // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight
-                                // to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match
-                                // a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
-                                doc._nativeHeight = (actualdH / (docheight || 1)) * Doc.NativeHeight(doc);
-                                doc._layout_autoHeight = false;
-                            } else {
-                                if (!doc._layout_fitWidth || preserveNativeDim) {
-                                    actualdW = (nwidth / nheight) * actualdH;
-                                    dXin && (dX = -dH * scale * (nwidth / nheight));
-                                    doc._width = actualdW;
-                                } else if (!modifyNativeDim || dragBotRight) {
-                                    doc._width = actualdW;
-                                }
-                            }
-                            if (!modifyNativeDim) {
-                                actualdH = (nheight / nwidth) * NumCast(doc._width); //, actualdH);
-                            }
-                            doc._height = actualdH;
-                        }
-                    } else {
-                        const rotCtr = [docwidth / 2, docheight / 2];
-                        const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
-
-                        const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '_scrollHeight']))) * docView.NativeDimScaling();
-                        dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
-                        dW && (doc._width = actualdW);
-                        dH && (doc._layout_autoHeight = false);
-
-                        const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
-                        const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._rotation) / 180) * Math.PI);
-                        doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc
-                        doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]);
-                    }
-                    doc.x = NumCast(doc.x) + dX;
-                    doc.y = NumCast(doc.y) + dY;
-                    doc._layout_modificationDate = new DateField();
-                }
-                const val = this._dragHeights.get(docView.layoutDoc);
-                if (val) this._dragHeights.set(docView.layoutDoc, { start: val.start, lowest: Math.min(val.lowest, NumCast(docView.layoutDoc._height)) });
-            })
+    // This realigns the doc's resize reference point with where it was before resizing it.
+    // This is needed, because the transformation for doc's with a rotation is screwy:
+    //    the top left of the doc is the 'origin', but the rotation happens about the center of the Doc.
+    // So resizing a rotated doc will cause it to shift -- this counteracts that shift by determine how
+    // the reference points shifted, and returning a translation to restore the reference point.
+    realignRefPt = (doc: Doc, refCent: number[], initWidth: number, initHeight: number) => {
+        const refCentPct = [refCent[0] / initWidth, refCent[1] / initHeight];
+        const rotRefStart = Utils.rotPt(
+            refCent[0] - initWidth / 2, // rotate reference pointe before scaling
+            refCent[1] - initHeight / 2,
+            (NumCast(doc._rotation) / 180) * Math.PI
         );
-        return false;
+        const rotRefEnd = Utils.rotPt(
+            refCentPct[0] * NumCast(doc._width) - NumCast(doc._width) / 2, // rotate reference point after scaling
+            refCentPct[1] * NumCast(doc._height) - NumCast(doc._height) / 2,
+            (NumCast(doc._rotation) / 180) * Math.PI
+        );
+        return {
+            deltaX: rotRefStart.x + initWidth / 2 - (rotRefEnd.x + NumCast(doc._width) / 2), //
+            deltaY: rotRefStart.y + initHeight / 2 - (rotRefEnd.y + NumCast(doc._height) / 2),
+        };
     };
 
     @action
     onPointerUp = (e: PointerEvent): void => {
         SnappingManager.SetIsResizing(undefined);
-        this._resizeHdlId = '';
+        SnappingManager.clearSnapLines();
         DocumentView.Interacting = false;
+        this._resizeHdlId = '';
         this._resizeUndo?.end();
-        SnappingManager.clearSnapLines();
 
         // detect layout_autoHeight gesture and apply
-        SelectionManager.Views()
-            .map(docView => ({ doc: docView.layoutDoc, hgts: this._dragHeights.get(docView.layoutDoc) }))
-            .filter(pair => pair.hgts && pair.hgts.lowest < pair.hgts.start && pair.hgts.lowest <= 20)
-            .forEach(pair => (pair.doc._layout_autoHeight = true));
+        SelectionManager.Docs().forEach(doc => NumCast(doc._height) < 20 && (doc._layout_autoHeight = true));
         //need to change points for resize, or else rotation/control points will fail.
         this._inkDragDocs
             .map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
             .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => {
-                Doc.GetProto(doc).data = new InkField(
-                    inkPts.map(
-                        (
-                            ipt // (new x — oldx) + newWidth * (oldxpoint /oldWidth)
-                        ) => ({
-                            X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
-                            Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
-                        })
-                    )
-                );
+                Doc.GetProto(doc).data = new InkField(inkPts.map(
+                    (ipt) => ({// (new x — oldx) + newWidth * (oldxpoint /oldWidth)
+                        X: NumCast(doc.x) - x + (NumCast(doc.width) * ipt.X) / width,
+                        Y: NumCast(doc.y) - y + (NumCast(doc.height) * ipt.Y) / height,
+                    }))); // prettier-ignore
                 Doc.SetNativeWidth(doc, undefined);
                 Doc.SetNativeHeight(doc, undefined);
             });
@@ -733,41 +627,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         return SelectionManager.Views().length > 1 ? '-multiple-' : '-unset-';
     }
 
-    @computed get hasIcons() {
-        return SelectionManager.Views().some(docView => docView.rootDoc.layout_fieldKey === 'layout_icon');
-    }
-
-    @observable _showRotCenter = false;
-    @observable _rotCenter = [0, 0];
     @computed get rotCenter() {
-        if (SelectionManager.Views().length) {
-            const seldocview = SelectionManager.Views()[0];
-            const loccenter = Utils.rotPt(
-                NumCast(seldocview.rootDoc.rotation_centerX) * NumCast(seldocview.layoutDoc._width),
-                NumCast(seldocview.rootDoc.rotation_centerY) * NumCast(seldocview.layoutDoc._height),
-                (NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI
-            );
-            return seldocview.props
-                .ScreenToLocalTransform()
-                .inverse()
-                .transformPoint(loccenter.x + NumCast(seldocview.layoutDoc._width) / 2, loccenter.y + NumCast(seldocview.layoutDoc._height) / 2);
+        const lastView = SelectionManager.Views().lastElement();
+        if (lastView) {
+            const invXf = lastView.props.ScreenToLocalTransform().inverse();
+            const seldoc = lastView.layoutDoc;
+            const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
+            return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
         }
         return this._rotCenter;
     }
 
-    @observable _showNothing = true;
-
     render() {
         const { b, r, x, y } = this.Bounds;
-        const bounds = { b, r, x, y };
         const seldocview = SelectionManager.Views().lastElement();
-        if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
+        if (SnappingManager.GetIsDragging() || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
             setTimeout(action(() => (this._showNothing = true)));
             return null;
         }
 
         // sharing
-        const acl = GetEffectiveAcl(!this.showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc);
+        const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc);
         const docShareMode = HierarchyMapping.get(acl)!.name;
         const shareMode = StrCast(docShareMode);
         var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
@@ -806,19 +686,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             </Tooltip>
         );
 
-        const leftBounds = this.props.boundsLeft;
-        const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
-        bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
-        bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
-        const borderRadiusDraggerWidth = 15;
-        bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth));
-        bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
-
-        const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView;
-        const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views().length == 1 ? NumCast(seldocview.rootDoc._rotation) : 0;
-
-        const resizerScheme = '';
+        const bounds = this.ClippedBounds;
+        const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
+        const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
+        const rotation = SelectionManager.Views().length == 1 ? seldocview.screenToLocalTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
@@ -862,7 +733,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 onPointerDown={e => e.stopPropagation()}
             />
         ) : (
-            <div className="documentDecorations-title" key="title" onPointerDown={e => e.stopPropagation}>
+            <div className="documentDecorations-title" key="title">
                 {hideTitle ? null : (
                     <span className="documentDecorations-titleSpan" onPointerDown={this.onTitleDown}>
                         {this.selectionTitle}
@@ -871,14 +742,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 {sharingMenu}
                 {!useLock ? null : (
                     <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
-                        <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown} onContextMenu={e => e.preventDefault()}>
+                        <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
                             <FontAwesomeIcon size="sm" icon="lock" />
                         </div>
                     </Tooltip>
                 )}
             </div>
         );
-        const freeformDoc = SelectionManager.Views().some(v => v.props.CollectionFreeFormDocumentView?.());
+        const centery = hideTitle ? 0 : this._titleHeight;
+        const transformOrigin = `${50}% calc(50% + ${centery / 2}px)`;
+        const freeformDoc = SelectionManager.Views().some(v => v.CollectionFreeFormDocumentView);
         return (
             <div className="documentDecorations" style={{ display: this._showNothing && !freeformDoc ? 'none' : undefined }}>
                 <div
@@ -888,30 +761,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         height: bounds.b - bounds.y + this._resizeBorderWidth + 'px',
                         left: bounds.x - this._resizeBorderWidth / 2,
                         top: bounds.y - this._resizeBorderWidth / 2,
-                        pointerEvents: DocumentDecorations.Instance.AddToSelection || DocumentView.Interacting ? 'none' : 'all',
+                        transformOrigin,
+                        background: SnappingManager.GetShiftKey() ? undefined : 'yellow',
+                        pointerEvents: SnappingManager.GetShiftKey() || DocumentView.Interacting ? 'none' : 'all',
                         display: SelectionManager.Views().length <= 1 || hideDecorations ? 'none' : undefined,
+                        transform: `rotate(${rotation}deg)`,
                     }}
                     onPointerDown={this.onBackgroundDown}
-                    onContextMenu={e => {
-                        e.preventDefault();
-                        e.stopPropagation();
-                    }}
                 />
                 {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : (
                     <div>
                         <div
                             className={`documentDecorations-container  ${this._showNothing ? 'showNothing' : ''}`}
-                            key="container"
                             style={{
                                 transform: `translate(${bounds.x - this._resizeBorderWidth / 2}px, ${bounds.y - this._resizeBorderWidth / 2 - this._titleHeight}px) rotate(${rotation}deg)`,
-                                transformOrigin: `50% calc(50% + 10px)`,
+                                transformOrigin,
                                 width: bounds.r - bounds.x + this._resizeBorderWidth + 'px',
                                 height: bounds.b - bounds.y + this._resizeBorderWidth + (this._showNothing ? 0 : this._titleHeight) + 'px',
                             }}>
                             <div
                                 className="documentDecorations-topbar"
                                 style={{
-                                    color: 'black',
                                     display: hideDeleteButton && hideTitle && hideOpenButton ? 'none' : undefined,
                                 }}
                                 onPointerDown={this.onContainerDown}>
@@ -922,37 +792,34 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             </div>
                             {hideResizers ? null : (
                                 <>
-                                    <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="l" className={`documentDecorations-leftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="c" className={`documentDecorations-centerCont ${resizerScheme}`}></div>
-                                    <div key="r" className={`documentDecorations-rightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="bl" className={`documentDecorations-bottomLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="b" className={`documentDecorations-bottomResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-                                    <div key="br" className={`documentDecorations-bottomRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={e => e.preventDefault()} />
-
-                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')}
+                                    <div key="tl" className="documentDecorations-topLeftResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="t" className="documentDecorations-topResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="tr" className="documentDecorations-topRightResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="l" className="documentDecorations-leftResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="c" className="documentDecorations-centerCont" />
+                                    <div key="r" className="documentDecorations-rightResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="bl" className="documentDecorations-bottomLeftResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="b" className="documentDecorations-bottomResizer" onPointerDown={this.onPointerDown} />
+                                    <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
+                                    {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement()
+                                        ? null
+                                        : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
                                 </>
                             )}
                             {useRounding && (
                                 <div
-                                    key="rad"
                                     className="documentDecorations-borderRadius"
                                     style={{
-                                        opacity: 0.5,
                                         background: `${this._isRounding ? Colors.MEDIUM_BLUE : SettingsManager.userColor}`,
                                         transform: `translate(${radiusHandleLocation ?? 0}px, ${(radiusHandleLocation ?? 0) + (this._showNothing ? 0 : this._titleHeight)}px)`,
                                     }}
                                     onPointerDown={this.onRadiusDown}
-                                    onContextMenu={e => e.preventDefault()}
                                 />
                             )}
 
                             {hideDocumentButtonBar || this._showNothing ? null : (
                                 <div
                                     className="link-button-container"
-                                    key="links"
                                     style={{
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 35d6d73e4..db6c00426 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
-import { NumCast } from '../../fields/Types';
+import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
@@ -30,20 +30,24 @@ import {
     SetActiveInkColor,
     SetActiveInkWidth,
 } from './InkingStroke';
-import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
-import { Touchable } from './Touchable';
 import { checkInksToGroup } from './global/globalScripts';
 import { DocumentView } from './nodes/DocumentView';
-import { RadialMenu } from './nodes/RadialMenu';
 
 interface GestureOverlayProps {
     isActive: boolean;
 }
 @observer
-export class GestureOverlay extends Touchable<GestureOverlayProps> {
+export class GestureOverlay extends React.Component<React.PropsWithChildren<GestureOverlayProps>> {
     static Instance: GestureOverlay;
     static Instances: GestureOverlay[] = [];
 
+    public static set RecognizeGestures(active) {
+        Doc.UserDoc().recognizeGestures = active;
+    }
+    public static get RecognizeGestures() {
+        return BoolCast(Doc.UserDoc().recognizeGestures);
+    }
+
     @observable public InkShape: Opt<GestureUtils.Gestures>;
     @observable public SavedColor?: string;
     @observable public SavedWidth?: number;
@@ -78,10 +82,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     private _inkToTextDoc: Doc | undefined;
     private thumbIdentifier?: number;
     private pointerIdentifier?: number;
-    private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
-    private _holdTimer: NodeJS.Timeout | undefined;
-
-    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     constructor(props: any) {
         super(props);
@@ -97,415 +97,12 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         GestureOverlay.Instance = this;
     };
 
-    // TODO: nda - add dragging groups with one finger drag and have to click into group to scroll within the group
-
-    /**
-     * Ignores all touch events that belong to a hand being held down.
-     */
-    getNewTouches(e: React.TouchEvent | TouchEvent) {
-        const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches);
-        const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches);
-        const nt: (React.Touch | Touch)[] = Array.from(e.touches);
-        this._hands.forEach(hand => {
-            for (let i = 0; i < e.targetTouches.length; i++) {
-                const pt = e.targetTouches.item(i);
-                if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
-                    ntt.splice(ntt.indexOf(pt), 1);
-                }
-            }
-
-            for (let i = 0; i < e.changedTouches.length; i++) {
-                const pt = e.changedTouches.item(i);
-                if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
-                    nct.splice(nct.indexOf(pt), 1);
-                }
-            }
-
-            for (let i = 0; i < e.touches.length; i++) {
-                const pt = e.touches.item(i);
-                if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) {
-                    nt.splice(nt.indexOf(pt), 1);
-                }
-            }
-        });
-        return { ntt, nct, nt };
-    }
-
-    onReactTouchStart = (te: React.TouchEvent) => {
-        document.removeEventListener('touchmove', this.onReactHoldTouchMove);
-        document.removeEventListener('touchend', this.onReactHoldTouchEnd);
-        if (RadialMenu.Instance?._display === true) {
-            te.preventDefault();
-            te.stopPropagation();
-            RadialMenu.Instance.closeMenu();
-            return;
-        }
-
-        // this chunk adds new touch targets to a map of pointer events; this helps us keep track of individual fingers
-        // so that we can know, for example, if two fingers are pinching out or in.
-        const actualPts: React.Touch[] = [];
-        for (let i = 0; i < te.touches.length; i++) {
-            const pt: any = te.touches.item(i);
-            actualPts.push(pt);
-            // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
-            // and this seems to be the only way of differentiating pen and touch on touch events
-            if (pt.radiusX > 1 && pt.radiusY > 1) {
-                InkTranscription.Instance.createInkGroup();
-                Doc.ActiveTool = InkTool.None;
-                this.prevPoints.set(pt.identifier, pt);
-            }
-        }
-
-        const ptsToDelete: number[] = [];
-        this.prevPoints.forEach(pt => {
-            if (!actualPts.includes(pt)) {
-                ptsToDelete.push(pt.identifier);
-            }
-        });
-
-        ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
-        const nts = this.getNewTouches(te);
-        // if there are fewer than five touch events, handle as a touch event
-        if (nts.nt.length < 5) {
-            const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
-            target?.dispatchEvent(
-                new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>('dashOnTouchStart', {
-                    bubbles: true,
-                    detail: {
-                        fingers: this.prevPoints.size,
-                        targetTouches: nts.ntt,
-                        touches: nts.nt,
-                        changedTouches: nts.nct,
-                        touchEvent: te,
-                    },
-                })
-            );
-            if (nts.nt.length === 1) {
-                // -- radial menu code --
-                this._holdTimer = setTimeout(() => {
-                    const target = document.elementFromPoint(te.changedTouches?.item(0).clientX, te.changedTouches?.item(0).clientY);
-                    const pt: any = te.touches[te.touches?.length - 1];
-                    if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) {
-                        target?.dispatchEvent(
-                            new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>('dashOnTouchHoldStart', {
-                                bubbles: true,
-                                detail: {
-                                    fingers: this.prevPoints.size,
-                                    targetTouches: nts.ntt,
-                                    touches: nts.nt,
-                                    changedTouches: nts.nct,
-                                    touchEvent: te,
-                                },
-                            })
-                        );
-                        this._holdTimer = undefined;
-                        document.removeEventListener('touchmove', this.onReactTouchMove);
-                        document.removeEventListener('touchend', this.onReactTouchEnd);
-                        document.removeEventListener('touchmove', this.onReactHoldTouchMove);
-                        document.removeEventListener('touchend', this.onReactHoldTouchEnd);
-                        document.addEventListener('touchmove', this.onReactHoldTouchMove);
-                        document.addEventListener('touchend', this.onReactHoldTouchEnd);
-                    }
-                }, 500);
-            } else {
-                this._holdTimer && clearTimeout(this._holdTimer);
-            }
-            document.removeEventListener('touchmove', this.onReactTouchMove);
-            document.removeEventListener('touchend', this.onReactTouchEnd);
-            document.addEventListener('touchmove', this.onReactTouchMove);
-            document.addEventListener('touchend', this.onReactTouchEnd);
-        }
-        // otherwise, handle as a hand event
-        else {
-            this.handleHandDown(te);
-            document.removeEventListener('touchmove', this.onReactTouchMove);
-            document.removeEventListener('touchend', this.onReactTouchEnd);
-        }
-    };
-
-    onReactTouchMove = (e: TouchEvent) => {
-        const nts: any = this.getNewTouches(e);
-        this._holdTimer && clearTimeout(this._holdTimer);
-        this._holdTimer = undefined;
-
-        document.dispatchEvent(
-            new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchMove', {
-                bubbles: true,
-                detail: {
-                    fingers: this.prevPoints.size,
-                    targetTouches: nts.ntt,
-                    touches: nts.nt,
-                    changedTouches: nts.nct,
-                    touchEvent: e,
-                },
-            })
-        );
-    };
-
-    onReactTouchEnd = (e: TouchEvent) => {
-        const nts: any = this.getNewTouches(e);
-        this._holdTimer && clearTimeout(this._holdTimer);
-        this._holdTimer = undefined;
-
-        document.dispatchEvent(
-            new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchEnd', {
-                bubbles: true,
-                detail: {
-                    fingers: this.prevPoints.size,
-                    targetTouches: nts.ntt,
-                    touches: nts.nt,
-                    changedTouches: nts.nct,
-                    touchEvent: e,
-                },
-            })
-        );
-
-        // cleanup any lingering pointers
-        for (let i = 0; i < e.changedTouches.length; i++) {
-            const pt = e.changedTouches.item(i);
-            if (pt) {
-                if (this.prevPoints.has(pt.identifier)) {
-                    this.prevPoints.delete(pt.identifier);
-                }
-            }
-        }
-
-        if (this.prevPoints.size === 0) {
-            document.removeEventListener('touchmove', this.onReactTouchMove);
-            document.removeEventListener('touchend', this.onReactTouchEnd);
-        }
-        e.stopPropagation();
-    };
-
-    handleHandDown = async (e: React.TouchEvent) => {
-        this._holdTimer && clearTimeout(this._holdTimer);
-
-        // this chunk of code helps us keep track of which touch events are associated with a hand event
-        // so that if a hand is held down, but a second hand is interacting with dash, the second hand's events
-        // won't interfere with the first hand's events.
-        const fingers = new Array<React.Touch>();
-        for (let i = 0; i < e.touches.length; i++) {
-            const pt: any = e.touches.item(i);
-            if (pt.radiusX > 1 && pt.radiusY > 1) {
-                for (let j = 0; j < e.targetTouches.length; j++) {
-                    const tPt = e.targetTouches.item(j);
-                    if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
-                        if (pt && this.prevPoints.has(pt.identifier)) {
-                            fingers.push(pt);
-                        }
-                    }
-                }
-            }
-        }
-
-        // this chunk of code determines whether this is a left hand or a right hand, as well as which pointer is the thumb and pointer
-        const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]);
-        const rightMost = Math.max(...fingers.map(f => f.clientX));
-        const leftMost = Math.min(...fingers.map(f => f.clientX));
-        let pointer: React.Touch | undefined;
-        // left hand
-        if (thumb.clientX === rightMost) {
-            pointer = fingers.reduce((a, v) => (a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v));
-        }
-        // right hand
-        else if (thumb.clientX === leftMost) {
-            pointer = fingers.reduce((a, v) => (a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v));
-        }
-        this.pointerIdentifier = pointer?.identifier;
-
-        runInAction(() => {
-            this._pointerY = pointer?.clientY;
-            if (thumb.identifier === this.thumbIdentifier) {
-                this._thumbX = thumb.clientX;
-                this._thumbY = thumb.clientY;
-                this._hands.set(thumb.identifier, fingers);
-                return;
-            }
-        });
-
-        this.thumbIdentifier = thumb?.identifier;
-        this._hands.set(thumb.identifier, fingers);
-
-        this.removeMoveListeners();
-        document.removeEventListener('touchmove', this.handleHandMove);
-        document.addEventListener('touchmove', this.handleHandMove);
-        document.removeEventListener('touchend', this.handleHandUp);
-        document.addEventListener('touchend', this.handleHandUp);
-    };
-
-    @action
-    handleHandMove = (e: TouchEvent) => {
-        // update pointer trackers
-        const fingers = new Array<React.Touch>();
-        for (let i = 0; i < e.touches.length; i++) {
-            const pt: any = e.touches.item(i);
-            if (pt.radiusX > 1 && pt.radiusY > 1) {
-                for (let j = 0; j < e.targetTouches.length; j++) {
-                    const tPt = e.targetTouches.item(j);
-                    if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
-                        if (pt && this.prevPoints.has(pt.identifier)) {
-                            this._hands.forEach(hand =>
-                                hand.some(f => {
-                                    if (f.identifier === pt.identifier) {
-                                        fingers.push(pt);
-                                    }
-                                })
-                            );
-                        }
-                    }
-                }
-            }
-        }
-        // update hand trackers
-        const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]);
-        if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) {
-            this._hands.set(thumb.identifier, fingers);
-        }
-
-        // loop through every changed pointer
-        for (let i = 0; i < e.changedTouches.length; i++) {
-            const pt = e.changedTouches.item(i);
-            // if the thumb was moved
-            if (pt && pt.identifier === this.thumbIdentifier && this._thumbY) {
-                if (this._thumbX && this._thumbY) {
-                    // moving a thumb horiz. changes the palette collection selection, moving vert. changes the selection of any menus on the current palette item
-                    const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY);
-                    if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) {
-                        if (Math.abs(pt.clientY - this._thumbY) > 10 * window.devicePixelRatio) {
-                            this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1), this._possibilities.length - 1);
-                        }
-                    }
-                }
-            }
-            // if the pointer finger was moved
-            if (pt && pt.identifier === this.pointerIdentifier) {
-                this._pointerY = pt.clientY;
-            }
-        }
-    };
-
-    @action
-    handleHandUp = (e: TouchEvent) => {
-        // sometimes, users may lift up their thumb or index finger if they can't stretch far enough to scroll an entire menu,
-        // so we don't want to just remove the palette when that happens
-        if (e.touches.length < 3) {
-            if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier);
-            this._palette = undefined;
-            this.thumbIdentifier = undefined;
-
-            // this chunk of code is for handling the ink to text toolglass
-            let scriptWorked = false;
-            if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
-                // if there is a text option selected, activate it
-                const selectedButton = this._possibilities[this._selectedIndex];
-                if (selectedButton) {
-                    selectedButton.props.onClick();
-                    scriptWorked = true;
-                }
-            }
-            // if there isn't a text option selected, dry the ink strokes into ink documents
-            if (!scriptWorked) {
-                this._strokes.forEach(s => {
-                    this.dispatchGesture(GestureUtils.Gestures.Stroke, s);
-                });
-            }
-
-            this._strokes = [];
-            this._points.length = 0;
-            this._possibilities = [];
-            document.removeEventListener('touchend', this.handleHandUp);
-        }
-    };
-
-    /**
-     * Code for radial menu
-     */
-    onReactHoldTouchMove = (e: TouchEvent) => {
-        document.removeEventListener('touchmove', this.onReactTouchMove);
-        document.removeEventListener('touchend', this.onReactTouchEnd);
-        document.removeEventListener('touchmove', this.onReactHoldTouchMove);
-        document.removeEventListener('touchend', this.onReactHoldTouchEnd);
-        document.addEventListener('touchmove', this.onReactHoldTouchMove);
-        document.addEventListener('touchend', this.onReactHoldTouchEnd);
-        const nts: any = this.getNewTouches(e);
-        if (this.prevPoints.size === 1 && this._holdTimer) {
-            clearTimeout(this._holdTimer);
-        }
-        document.dispatchEvent(
-            new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchHoldMove', {
-                bubbles: true,
-                detail: {
-                    fingers: this.prevPoints.size,
-                    targetTouches: nts.ntt,
-                    touches: nts.nt,
-                    changedTouches: nts.nct,
-                    touchEvent: e,
-                },
-            })
-        );
-    };
-
-    /**
-     * Code for radial menu
-     */
-    onReactHoldTouchEnd = (e: TouchEvent) => {
-        const nts: any = this.getNewTouches(e);
-        if (this.prevPoints.size === 1 && this._holdTimer) {
-            clearTimeout(this._holdTimer);
-            this._holdTimer = undefined;
-        }
-        document.dispatchEvent(
-            new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>('dashOnTouchHoldEnd', {
-                bubbles: true,
-                detail: {
-                    fingers: this.prevPoints.size,
-                    targetTouches: nts.ntt,
-                    touches: nts.nt,
-                    changedTouches: nts.nct,
-                    touchEvent: e,
-                },
-            })
-        );
-        for (let i = 0; i < e.changedTouches.length; i++) {
-            const pt = e.changedTouches.item(i);
-            if (pt) {
-                if (this.prevPoints.has(pt.identifier)) {
-                    this.prevPoints.delete(pt.identifier);
-                }
-            }
-        }
-
-        document.removeEventListener('touchmove', this.onReactHoldTouchMove);
-        document.removeEventListener('touchend', this.onReactHoldTouchEnd);
-
-        e.stopPropagation();
-    };
-
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            setupMoveUpEvents(
-                this,
-                e,
-                returnFalse,
-                returnFalse,
-                action((e: PointerEvent, doubleTap?: boolean) => {
-                    if (doubleTap) {
-                        InkTranscription.Instance.createInkGroup();
-                        Doc.ActiveTool = InkTool.None;
-                        return;
-                    }
-                })
-            );
-        }
         if (!(e.target as any)?.className?.toString().startsWith('lm_')) {
-            if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-                if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
-                    Doc.ActiveTool = InkTool.Write;
-                }
+            if ([InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
                 this._points.push({ X: e.clientX, Y: e.clientY });
                 setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
-                // if (Doc.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
             }
         }
     };
@@ -546,41 +143,8 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
 
-            const initialPoint = this._points[0];
-            const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
-            const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
-
-            // if a toolglass is selected and the stroke starts within the toolglass boundaries
-            if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
-                switch (this.Tool) {
-                    case ToolglassTools.InkToText:
-                        this._strokes.push(this._points.slice());
-                        CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => {
-                            const wordResults = results.filter((r: any) => r.category === 'line');
-                            const possibilities: string[] = [];
-                            for (const wR of wordResults) {
-                                if (wR?.recognizedText) {
-                                    possibilities.push(wR?.recognizedText);
-                                }
-                                possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString));
-                            }
-                            const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => GestureOverlay.getBounds(s).right));
-                            const l = Math.min(this.svgBounds.left, ...this._strokes.map(s => GestureOverlay.getBounds(s).left));
-                            const t = Math.min(this.svgBounds.top, ...this._strokes.map(s => GestureOverlay.getBounds(s).top));
-
-                            // if we receive any word results from cognitive services, display them
-                            runInAction(() => {
-                                this._possibilities = possibilities.map(p => <TouchScrollableMenuItem text={p} onClick={() => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />);
-                            });
-                        });
-                        break;
-                    case ToolglassTools.IgnoreGesture:
-                        this.dispatchGesture(GestureUtils.Gestures.Stroke);
-                        break;
-                }
-            }
             //if any of the shape is activated in the CollectionFreeFormViewChrome
-            else if (this.InkShape) {
+            if (this.InkShape) {
                 this.makeBezierPolygon(this.InkShape, false);
                 this.dispatchGesture(this.InkShape);
                 this.primCreated();
@@ -590,7 +154,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 // need to decide when to turn gestures back on
                 const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points));
                 let actionPerformed = false;
-                if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) {
+                if (GestureOverlay.RecognizeGestures && result && result.Score > 0.7) {
                     switch (result.Name) {
                         case GestureUtils.Gestures.Line:
                         case GestureUtils.Gestures.Triangle:
@@ -938,7 +502,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
 
     render() {
         return (
-            <div className="gestureOverlay-cont" style={{ pointerEvents: this.props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+            <div className="gestureOverlay-cont" style={{ pointerEvents: this.props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
                 {this.elements}
 
@@ -948,7 +512,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                         height: this.height,
                         width: this.height,
                         pointerEvents: this._clipboardDoc ? 'unset' : 'none',
-                        touchAction: this._clipboardDoc ? 'unset' : 'none',
+                        touchAction: 'none',
                         transform: `translate(${this._thumbX}px, ${(this._thumbY || 0) - this.height} px)`,
                     }}>
                     {this._clipboardDoc}
@@ -964,7 +528,6 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                         display: this.showBounds ? 'unset' : 'none',
                     }}
                 />
-                <TouchScrollableMenu options={this._possibilities} bounds={this.svgBounds} selectedIndex={this._selectedIndex} x={this._menuX} y={this._menuY} />
             </div>
         );
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 7b693c8da..87f81fe76 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -33,6 +33,7 @@ type KeyControlInfo = {
     stopPropagation: boolean;
 };
 
+export let CtrlKey = false;
 export class KeyManager {
     public static Instance: KeyManager = new KeyManager();
     private router = new Map<string, KeyHandler>();
@@ -49,11 +50,19 @@ export class KeyManager {
     }
 
     public unhandle = action((e: KeyboardEvent) => {
-        if (e.key?.toLowerCase() === 'shift') runInAction(() => (DocumentDecorations.Instance.AddToSelection = false));
+        e.key === 'Control' && (CtrlKey = false);
+    });
+    public handleModifiers = action((e: KeyboardEvent) => {
+        if (e.shiftKey) SnappingManager.SetShiftKey(true);
+        if (e.ctrlKey) SnappingManager.SetCtrlKey(true);
+    });
+    public unhandleModifiers = action((e: KeyboardEvent) => {
+        if (!e.shiftKey) SnappingManager.SetShiftKey(false);
+        if (!e.ctrlKey) SnappingManager.SetCtrlKey(false);
     });
 
     public handle = action((e: KeyboardEvent) => {
-        if (e.key?.toLowerCase() === 'shift') DocumentDecorations.Instance.AddToSelection = true;
+        e.key === 'Control' && (CtrlKey = true);
         //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true);
         const keyname = e.key && e.key.toLowerCase();
         this.handleGreedy(keyname);
@@ -81,41 +90,32 @@ export class KeyManager {
         }
     });
 
+    nudge = (x: number, y: number, label: string) => {
+        const nudgeable = SelectionManager.Views().some(dv => dv.CollectionFreeFormDocumentView?.nudge);
+        nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.CollectionFreeFormDocumentView?.nudge(x, y)), label);
+        return { stopPropagation: nudgeable, preventDefault: nudgeable };
+    };
+
     private unmodified = action((keyname: string, e: KeyboardEvent) => {
-        const hasFffView = SelectionManager.Views().some(dv => dv.props.CollectionFreeFormDocumentView?.());
         switch (keyname) {
             case 'u':
-                if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
-                    return { stopPropagation: false, preventDefault: false };
+                if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
+                    const ungroupings = SelectionManager.Views();
+                    UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
+                    SelectionManager.DeselectAll();
                 }
-
-                const ungroupings = SelectionManager.Views().slice();
-                UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup');
-                SelectionManager.DeselectAll();
                 break;
             case 'g':
-                if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
-                    return { stopPropagation: false, preventDefault: false };
-                }
-
-                const groupings = SelectionManager.Views().slice();
-                const randomGroup = random(0, 1000);
-                const collectionView = groupings.reduce(
-                    (col, g) => (col === null || g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView === col ? g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined),
-                    null as null | undefined | CollectionFreeFormView
-                );
-                if (collectionView) {
-                    UndoManager.RunInBatch(() => {
-                        collectionView._marqueeViewRef.current?.collection(
-                            e,
-                            true,
-                            groupings.map(g => g.rootDoc)
-                        );
-                    }, 'grouping');
-                    break;
+                if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
+                    const selected = SelectionManager.Views();
+                    const collectionView = selected.reduce((col, dv) => (col === null || dv.CollectionFreeFormView === col ? dv.CollectionFreeFormView : undefined), null as null | undefined | CollectionFreeFormView);
+                    if (collectionView) {
+                        UndoManager.RunInBatch(() =>
+                            collectionView._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs())
+                        , 'grouping');
+                        break;
+                    }
                 }
-                UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group');
-                SelectionManager.DeselectAll();
                 break;
             case ' ':
                 // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI
@@ -139,7 +139,7 @@ export class KeyManager {
                 }
                 if (doDeselect) {
                     SelectionManager.DeselectAll();
-                    LightboxView.SetLightboxDoc(undefined);
+                    LightboxView.Instance.SetLightboxDoc(undefined);
                 }
                 // DictationManager.Controls.stop();
                 GoogleAuthenticationManager.Instance.cancel();
@@ -158,25 +158,17 @@ export class KeyManager {
             case 'backspace':
                 if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
                     if (LightboxView.LightboxDoc) {
-                        LightboxView.SetLightboxDoc(undefined);
+                        LightboxView.Instance.SetLightboxDoc(undefined);
                         SelectionManager.DeselectAll();
                     } else DocumentDecorations.Instance.onCloseClick(true);
                     return { stopPropagation: true, preventDefault: true };
                 }
                 break;
-            case 'arrowleft':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), 'nudge left');
-                return { stopPropagation: hasFffView, preventDefault: hasFffView };
-            case 'arrowright':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), 'nudge right');
-                return { stopPropagation: hasFffView, preventDefault: hasFffView };
-            case 'arrowup':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), 'nudge up');
-                return { stopPropagation: hasFffView, preventDefault: hasFffView };
-            case 'arrowdown':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), 'nudge down');
-                return { stopPropagation: hasFffView, preventDefault: hasFffView };
-        }
+            case 'arrowleft':  return this.nudge(-1,0, 'nudge left')
+            case 'arrowright': return this.nudge(1,0, 'nudge right');
+            case 'arrowup':    return this.nudge(0, -1, 'nudge up');
+            case 'arrowdown':  return this.nudge(0, 1, 'nudge down');
+        } // prettier-ignore
 
         return {
             stopPropagation: false,
@@ -185,37 +177,29 @@ export class KeyManager {
     });
 
     private shift = action((keyname: string) => {
-        const stopPropagation = false;
-        const preventDefault = false;
-
         switch (keyname) {
-            case 'arrowleft':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), 'nudge left');
-                break;
-            case 'arrowright':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), 'nudge right');
-                break;
-            case 'arrowup':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), 'nudge up');
-                break;
-            case 'arrowdown':
-                UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down');
+            case 'arrowleft':  return this.nudge(-10,0, 'nudge left');
+            case 'arrowright': return this.nudge(10, 0, 'nudge right');
+            case 'arrowup':    return this.nudge(0, -10, 'nudge up');
+            case 'arrowdown':  return this.nudge(0, 10, 'nudge down');
+            case 'u' : 
+                if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
+                    UndoManager.RunInBatch(() => SelectionManager.Docs().forEach(doc => (doc.group = undefined)), 'unggroup');
+                    SelectionManager.DeselectAll();
+                }
                 break;
             case 'g':
-                if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
-                    return { stopPropagation: false, preventDefault: false };
+                if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') {
+                    const randomGroup = random(0, 1000);
+                    UndoManager.RunInBatch(() => SelectionManager.Docs().forEach(doc => (doc.group = randomGroup)), 'group');
+                    SelectionManager.DeselectAll();
                 }
-
-                const groupings = SelectionManager.Views().slice();
-                const randomGroup = random(0, 1000);
-                UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group');
-                SelectionManager.DeselectAll();
                 break;
-        }
+        } // prettier-ignore
 
         return {
-            stopPropagation: stopPropagation,
-            preventDefault: preventDefault,
+            stopPropagation: false,
+            preventDefault: false,
         };
     });
 
@@ -227,7 +211,7 @@ export class KeyManager {
             case 'ƒ':
             case 'f':
                 const dv = SelectionManager.Views()?.[0];
-                UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), 'float');
+                UndoManager.RunInBatch(() => dv.CollectionFreeFormDocumentView?.float(), 'float');
         }
 
         return {
@@ -262,7 +246,7 @@ export class KeyManager {
                 PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv }));
                 break;
             case 'i':
-                    const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
+                const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports);
                 if (importBtn) {
                     MainView.Instance.selectMenu(importBtn);
                 }
@@ -284,10 +268,10 @@ export class KeyManager {
                 }
                 break;
             case 'e':
-                Doc.ActiveTool = (Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser);
+                Doc.ActiveTool = Doc.ActiveTool === InkTool.Eraser ? InkTool.None : InkTool.Eraser;
                 break;
             case 'p':
-                Doc.ActiveTool = (Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen);
+                Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
                 break;
             case 'r':
                 preventDefault = false;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 0d7f7ebd8..e5141b7f4 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -12,6 +12,7 @@ import { UndoManager } from '../util/UndoManager';
 import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
+import { SnappingManager } from '../util/SnappingManager';
 
 export interface InkControlProps {
     inkDoc: Doc;
@@ -190,6 +191,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
 
     @action
     dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
+        SnappingManager.SetIsDragging(true);
         setupMoveUpEvents(
             this,
             e,
@@ -211,6 +213,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
                 return false;
             }),
             action(() => {
+                SnappingManager.SetIsDragging(false);
                 this.props.inkView.controlUndo?.end();
                 this.props.inkView.controlUndo = undefined;
                 UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']);
@@ -237,8 +240,8 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
         );
         return (
             <svg>
-                {hdl('start', this.props.startPt(), (e: React.PointerEvent) => this.dragRotate(e, this.props.startPt, this.props.endPt))}
-                {hdl('end', this.props.endPt(), (e: React.PointerEvent) => this.dragRotate(e, this.props.endPt, this.props.startPt))}
+                {hdl('start', this.props.startPt(), e => this.dragRotate(e, this.props.startPt, this.props.endPt))}
+                {hdl('end', this.props.endPt(), e => this.dragRotate(e, this.props.endPt, this.props.startPt))}
             </svg>
         );
     }
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 736ca8d90..62165bc48 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -372,8 +372,8 @@ export class InkStrokeProperties {
     };
 
     snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => {
-        const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-        const containingDocView = inkView.props.CollectionFreeFormDocumentView?.().props.DocumentView?.();
+        const containingCollection = inkView.CollectionFreeFormView;
+        const containingDocView = containingCollection?.props.DocumentView?.();
         containingCollection?.childDocs
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 258bfad66..17136a737 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -2,7 +2,6 @@ import * as iink from 'iink-js';
 import { action, observable } from 'mobx';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../fields/InkField';
 import { Cast, DateCast, NumCast } from '../../fields/Types';
 import { aggregateBounds } from '../../Utils';
@@ -287,8 +286,8 @@ export class InkTranscription extends React.Component {
             action(d => {
                 const x = NumCast(d.x);
                 const y = NumCast(d.y);
-                const width = d[Width]();
-                const height = d[Height]();
+                const width = NumCast(d._width);
+                const height = NumCast(d._height);
                 bounds.push({ x, y, width, height });
             })
         );
@@ -327,8 +326,8 @@ export class InkTranscription extends React.Component {
         // Gets a collection based on the selected nodes using a marquee view ref
         const newCollection = marqViewRef?.getCollection(selected, undefined, true);
         if (newCollection) {
-            newCollection.height = newCollection[Height]();
-            newCollection.width = newCollection[Width]();
+            newCollection.width = NumCast(newCollection._width);
+            newCollection.height = NumCast(newCollection._height);
             // if the grouping we are creating is an individual word
             if (word) {
                 newCollection.title = word;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d26c7761e..cf565abc8 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -24,7 +24,6 @@ import React = require('react');
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
-import { Height, Width } from '../../fields/DocSymbols';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
@@ -382,10 +381,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const fillColor = this.fillColor;
 
         // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be.
-        if (isInkMask && (this.layoutDoc[Width]() !== Math.round(this.layoutDoc[Width]()) || this.layoutDoc[Height]() !== Math.round(this.layoutDoc[Height]()))) {
+        if (isInkMask && (this.layoutDoc._width !== Math.round(NumCast(this.layoutDoc._width)) || this.layoutDoc._height !== Math.round(NumCast(this.layoutDoc._height)))) {
             setTimeout(() => {
-                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[Width]()));
-                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[Height]()));
+                this.layoutDoc._width = Math.round(NumCast(this.layoutDoc._width));
+                this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
             });
         }
         const highlight = !this.controlUndo && this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
@@ -467,7 +466,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 <svg
                     className="inkStroke"
                     style={{
-                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().Rot)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
@@ -481,7 +480,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
                             pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
-                            width: this.layoutDoc[Width](),
+                            width: NumCast(this.layoutDoc._width),
                             transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 93eaec959..c91f8455a 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,12 +1,13 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
 import { InkTool } from '../../fields/InkField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils';
+import { Cast, NumCast } from '../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, Utils } from '../../Utils';
 import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { LinkManager } from '../util/LinkManager';
@@ -27,216 +28,210 @@ interface LightboxViewProps {
     maxBorder: number[];
 }
 
-type LightboxSavedState = {
-    panX: Opt<number>;
-    panY: Opt<number>;
-    scale: Opt<number>;
-    scrollTop: Opt<number>;
-    layout_fieldKey: Opt<string>;
-};
+const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_scrollTop', 'layout_fieldKey'];
+type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
-    @computed public static get LightboxDoc() {
-        return this._doc;
+    public static IsLightboxDocView(path: DocumentView[]) { return (path ?? []).includes(LightboxView.Instance?._docView!); } // prettier-ignore
+    public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore
+    static Instance: LightboxView;
+    private _path: {
+        doc: Opt<Doc>; //
+        target: Opt<Doc>;
+        history: { doc: Doc; target?: Doc }[];
+        future: Doc[];
+        saved: LightboxSavedState;
+    }[] = [];
+    private _savedState: LightboxSavedState = {};
+    private _history: { doc: Doc; target?: Doc }[] = [];
+    @observable private _future: Doc[] = [];
+    @observable private _layoutTemplate: Opt<Doc>;
+    @observable private _layoutTemplateString: Opt<string>;
+    @observable private _doc: Opt<Doc>;
+    @observable private _docTarget: Opt<Doc>;
+    @observable private _docView: Opt<DocumentView>;
+
+    @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); } // prettier-ignore
+    @computed get topBorder()  { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); } // prettier-ignore
+
+    constructor(props: any) {
+        super(props);
+        if (LightboxView.Instance) console.log('SDFSFASFASFSALFKJD:SLFJS:LDFJKS:LFJS:LDJFL:SDFJL:SDJF:LSJ');
+        LightboxView.Instance = this;
     }
-    private static LightboxDocTemplate = () => LightboxView._layoutTemplate;
-    @observable private static _layoutTemplate: Opt<Doc>;
-    @observable private static _layoutTemplateString: Opt<string>;
-    @observable private static _doc: Opt<Doc>;
-    @observable private static _docTarget: Opt<Doc>;
-    @observable private static _childFilters: string[] = []; // filters
-    private static _savedState: Opt<LightboxSavedState>;
-    private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
-    @observable private static _future: Opt<Doc[]> = [];
-    @observable private static _docView: Opt<DocumentView>;
-    static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
-    @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
-        if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
-            if (this._savedState.panX !== undefined) this.LightboxDoc._freeform_panX = this._savedState.panX;
-            if (this._savedState.panY !== undefined) this.LightboxDoc._freeform_panY = this._savedState.panY;
-            if (this._savedState.scrollTop !== undefined) this.LightboxDoc._layout_scrollTop = this._savedState.scrollTop;
-            if (this._savedState.scale !== undefined) this.LightboxDoc._freeform_scale = this._savedState.scale;
-            this.LightboxDoc.layout_fieldKey = this._savedState.layout_fieldKey ? this._savedState.layout_fieldKey : undefined;
-        }
-        if (!doc) {
-            this._childFilters && (this._childFilters.length = 0);
-            this._future = this._history = [];
-            Doc.ActiveTool = InkTool.None;
-            DocumentView.ExploreMode = false;
-        } else {
+
+    @action
+    public SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
+        const lightDoc = this._doc;
+        lightDoc && lightDoc !== doc && savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+        this._savedState = {};
+
+        if (doc) {
+            lightDoc !== doc && savedKeys.map(key => (this._savedState[key] = Doc.Get(doc, key, true)));
             const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
             CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
-            //TabDocView.PinDoc(doc, { hidePresBox: true });
-            this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
-            if (doc !== LightboxView.LightboxDoc) {
-                this._savedState = {
-                    layout_fieldKey: StrCast(doc.layout_fieldKey),
-                    panX: Cast(doc.freeform_panX, 'number', null),
-                    panY: Cast(doc.freeform_panY, 'number', null),
-                    scale: Cast(doc.freeform_scale, 'number', null),
-                    scrollTop: Cast(doc.layout_scrollTop, 'number', null),
-                };
-            }
+            this._history.push({ doc, target });
+        } else {
+            this._future = [];
+            this._history = [];
+            Doc.ActiveTool = InkTool.None;
+            DocumentView.ExploreMode = false;
         }
+        SelectionManager.DeselectAll();
         if (future) {
-            this._future = [
-                ...(this._future ?? []),
-                ...(this.LightboxDoc ? [this.LightboxDoc] : []),
+            this._future.push(
+                ...(this._doc ? [this._doc] : []),
                 ...future
                     .slice()
                     .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))
-                    .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length),
-            ];
+                    .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length)
+            );
         }
         this._doc = doc;
         this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined;
         if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) {
             doc.layout_fieldKey = layoutTemplate;
         }
-        this._docTarget = target || doc;
+        this._docTarget = target ?? doc;
 
         return true;
     }
-    public static IsLightboxDocView(path: DocumentView[]) {
-        return (path ?? []).includes(this._docView!);
-    }
-    @computed get leftBorder() {
-        return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]);
-    }
-    @computed get topBorder() {
-        return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]);
-    }
-    lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
-    lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
-    lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
-    navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => {
-        return (
-            <div
-                className="lightboxView-navBtn-frame"
-                style={{
-                    display: display(),
-                    left,
-                    width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
-                    bottom,
-                }}>
-                <div className="lightboxView-navBtn" title={color} style={{ top, color: SettingsManager.userColor, background: undefined }} onClick={click}>
-                    <div style={{ height: 10 }}>{color}</div>
-                    <FontAwesomeIcon icon={icon as any} size="3x" />
-                </div>
-            </div>
-        );
-    };
-    public static GetSavedState(doc: Doc) {
-        return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined;
-    }
 
-    // adds a cookie to the lightbox view - the cookie becomes part of a filter which will display any documents whose cookie metadata field matches this cookie
-    @action
-    public static SetCookie(cookie: string) {
-        if (this.LightboxDoc && cookie) {
-            this._childFilters = (f => (this._childFilters ? [this._childFilters.push(f) as any, this._childFilters][1] : [f]))(`cookies:${cookie}:provide`);
-        }
-    }
-    public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
-        SelectionManager.DeselectAll();
-        return LightboxView.SetLightboxDoc(
+    public AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) =>
+        this.SetLightboxDoc(
             doc,
             undefined,
-            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(LightboxView._future ?? [])].sort(
-                (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
+            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort(
+                (a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
             ),
             layoutTemplate
         );
-    };
-    childFilters = () => LightboxView._childFilters || [];
-    addDocTab = LightboxView.AddDocTab;
-    @action public static Next() {
-        const doc = LightboxView._doc!;
-        const target = (LightboxView._docTarget = this._future?.pop());
+    @action
+    next = () => {
+        const lightDoc = this._doc;
+        if (!lightDoc) return;
+        const target = (this._docTarget = this._future.pop());
         const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
         if (targetDocView && target) {
             const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement();
             l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen');
             DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
-            if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target });
+            if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target });
+        } else if (!target && this._path.length) {
+            savedKeys.forEach(key => (lightDoc[key] = this._savedState[key]));
+            this._path.pop();
         } else {
-            if (!target && LightboxView.path.length) {
-                const saved = LightboxView._savedState;
-                if (LightboxView.LightboxDoc && saved) {
-                    LightboxView.LightboxDoc._freeform_panX = saved.panX;
-                    LightboxView.LightboxDoc._freeform_panY = saved.panY;
-                    LightboxView.LightboxDoc._freeform_scale = saved.scale;
-                    LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop;
-                }
-                const pop = LightboxView.path.pop();
-                if (pop) {
-                    LightboxView._doc = pop.doc;
-                    LightboxView._docTarget = pop.target;
-                    LightboxView._future = pop.future;
-                    LightboxView._history = pop.history;
-                    LightboxView._savedState = pop.saved;
-                }
-            } else {
-                LightboxView.SetLightboxDoc(target);
-            }
+            this.SetLightboxDoc(target);
         }
-    }
-
-    @action public static Previous() {
-        const previous = LightboxView._history?.pop();
-        if (!previous || !LightboxView._history?.length) {
-            LightboxView.SetLightboxDoc(undefined);
+    };
+    @action
+    previous = () => {
+        const previous = this._history.pop();
+        if (!previous || !this._history.length) {
+            this.SetLightboxDoc(undefined);
             return;
         }
-        const { doc, target } = LightboxView._history?.lastElement();
+        const { doc, target } = this._history.lastElement();
         const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
         if (docView) {
-            LightboxView._docTarget = target;
+            this._docTarget = target;
             target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 });
         } else {
-            LightboxView.SetLightboxDoc(doc, target);
+            this.SetLightboxDoc(doc, target);
         }
-        if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc);
-    }
+        if (this._future.lastElement() !== previous.target || previous.doc) this._future.push(previous.target || previous.doc);
+    };
     @action
     stepInto = () => {
-        LightboxView.path.push({
-            doc: LightboxView.LightboxDoc,
-            target: LightboxView._docTarget,
-            future: LightboxView._future,
-            history: LightboxView._history,
-            saved: LightboxView._savedState,
+        this._path.push({
+            doc: this._doc,
+            target: this._docTarget,
+            future: this._future,
+            history: this._history,
+            saved: this._savedState,
         });
-        const coll = LightboxView._docTarget;
-        if (coll) {
-            const fieldKey = Doc.LayoutFieldKey(coll);
-            const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])];
-            const links = LinkManager.Links(coll)
-                .map(link => LinkManager.getOppositeAnchor(link, coll))
-                .filter(doc => doc)
-                .map(doc => doc!);
-            LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links);
+        if (this._docTarget) {
+            const fieldKey = Doc.LayoutFieldKey(this._docTarget);
+            const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])];
+            const links = LinkManager.Links(this._docTarget)
+                .map(link => LinkManager.getOppositeAnchor(link, this._docTarget!)!)
+                .filter(doc => doc);
+            this.SetLightboxDoc(this._docTarget, undefined, contents.length ? contents : links);
+        }
+    };
+
+    downloadDoc = () => {
+        const lightDoc = this._docTarget ?? this._doc;
+        if (lightDoc) {
+            Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightDoc);
+            CollectionDockingView.AddSplit(lightDoc, OpenWhereMod.none);
+            this.SetLightboxDoc(undefined);
         }
     };
+    toggleFitWidth = () => this._doc && (this._doc._layout_fitWidth = !this._doc._layout_fitWidth);
+    togglePen = () => (Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen);
+    toggleExplore = () => (DocumentView.ExploreMode = !DocumentView.ExploreMode);
 
-    future = () => LightboxView._future;
+    lightboxDoc = () => this._doc;
+    lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2;
+    lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2;
+    lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1);
+    lightboxDocTemplate = () => this._layoutTemplate;
+    future = () => this._future;
+
+    renderNavBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => {
+        return (
+            <div
+                className="lightboxView-navBtn-frame"
+                style={{
+                    display: display ? '' : 'none',
+                    left,
+                    width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
+                    bottom,
+                }}>
+                <div
+                    className="lightboxView-navBtn"
+                    title={color}
+                    style={{ top, color: SettingsManager.userColor, background: undefined }}
+                    onClick={e => {
+                        e.stopPropagation();
+                        click();
+                    }}>
+                    <div style={{ height: 10 }}>{color}</div>
+                    <FontAwesomeIcon icon={icon} size="3x" />
+                </div>
+            </div>
+        );
+    };
     render() {
         let downx = 0,
             downy = 0;
-        return !LightboxView.LightboxDoc ? null : (
+        const toggleBtn = (classname: string, tooltip: string, toggleBackground: any, icon: IconProp, icon2: IconProp | string, onClick: () => void) => (
+            <div className={classname}>
+                <Toggle
+                    tooltip={tooltip}
+                    color={SettingsManager.userColor}
+                    background={toggleBackground ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
+                    toggleType={ToggleType.BUTTON}
+                    type={Type.TERT}
+                    icon={<FontAwesomeIcon icon={toggleBackground ? icon : (icon2 as IconProp) || icon} size="sm" />}
+                    onClick={e => {
+                        e.stopPropagation();
+                        runInAction(onClick);
+                    }}
+                />
+            </div>
+        );
+        return !this._doc ? null : (
             <div
                 className="lightboxView-frame"
+                style={{ background: SettingsManager.userBackgroundColor }}
                 onPointerDown={e => {
                     downx = e.clientX;
                     downy = e.clientY;
                 }}
-                style={{ background: SettingsManager.userBackgroundColor }}
-                onClick={e => {
-                    if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) {
-                        LightboxView.SetLightboxDoc(undefined);
-                    }
-                }}>
+                onClick={e => Utils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
                 <div
                     className="lightboxView-contents"
                     style={{
@@ -248,16 +243,14 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         background: SettingsManager.userBackgroundColor,
                         color: SettingsManager.userColor,
                     }}>
-                    {/* <CollectionMenu /> TODO:glr This is where it would go*/}
-
                     <GestureOverlay isActive={true}>
                         <DocumentView
-                            ref={action((r: DocumentView | null) => (LightboxView._docView = r !== null ? r : undefined))}
-                            Document={LightboxView.LightboxDoc}
+                            ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
+                            Document={this._doc}
                             DataDoc={undefined}
                             PanelWidth={this.lightboxWidth}
                             PanelHeight={this.lightboxHeight}
-                            LayoutTemplate={LightboxView.LightboxDocTemplate}
+                            LayoutTemplate={this.lightboxDocTemplate}
                             isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
                             isContentActive={returnTrue}
                             styleProvider={DefaultStyleProvider}
@@ -265,13 +258,13 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             renderDepth={0}
                             rootSelected={returnTrue}
                             docViewPath={returnEmptyDoclist}
-                            childFilters={this.childFilters}
+                            childFilters={returnEmptyFilter}
                             childFiltersByRanges={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             addDocument={undefined}
                             removeDocument={undefined}
                             whenChildContentsActiveChanged={emptyFunction}
-                            addDocTab={this.addDocTab}
+                            addDocTab={this.AddDocTab}
                             pinToPres={TabDocView.PinDoc}
                             bringToFront={emptyFunction}
                             onBrowseClick={DocumentView.exploreMode}
@@ -280,116 +273,34 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     </GestureOverlay>
                 </div>
 
-                {this.navBtn(
-                    0,
-                    undefined,
-                    this.props.PanelHeight / 2 - 12.5,
-                    'chevron-left',
-                    () => (LightboxView.LightboxDoc && LightboxView._history?.length ? '' : 'none'),
-                    e => {
-                        e.stopPropagation();
-                        LightboxView.Previous();
-                    }
-                )}
-                {this.navBtn(
+                {this.renderNavBtn(0, undefined, this.props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length, this.previous)}
+                {this.renderNavBtn(
                     this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]),
                     undefined,
                     this.props.PanelHeight / 2 - 12.5,
                     'chevron-right',
-                    () => (LightboxView.LightboxDoc && LightboxView._future?.length ? '' : 'none'),
-                    e => {
-                        e.stopPropagation();
-                        LightboxView.Next();
-                    },
-                    this.future()?.length.toString()
+                    this._doc && this._future.length,
+                    this.next,
+                    this.future().length.toString()
                 )}
-                <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} />
-                <div className="lightboxView-navBtn">
-                    <Toggle
-                        tooltip="toggle reading view"
-                        color={SettingsManager.userColor}
-                        background={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth) ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
-                        toggleType={ToggleType.BUTTON}
-                        type={Type.TERT}
-                        toggleStatus={BoolCast(LightboxView.LightboxDoc!._layout_fitWidth)}
-                        onClick={e => {
-                            e.stopPropagation();
-                            LightboxView.LightboxDoc!._layout_fitWidth = !LightboxView.LightboxDoc!._layout_fitWidth;
-                        }}
-                        icon={<FontAwesomeIcon icon={LightboxView.LightboxDoc?._layout_fitWidth ? 'book-open' : 'book'} size="sm" />}
-                    />
-                </div>
-                <div className="lightboxView-tabBtn">
-                    <Toggle
-                        tooltip="open document in a tab"
-                        color={SettingsManager.userColor}
-                        background={SettingsManager.userBackgroundColor}
-                        toggleType={ToggleType.BUTTON}
-                        type={Type.TERT}
-                        icon={<FontAwesomeIcon icon="file-download" size="sm" />}
-                        onClick={e => {
-                            const lightdoc = LightboxView._docTarget || LightboxView._doc!;
-                            e.stopPropagation();
-                            Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightdoc);
-                            CollectionDockingView.AddSplit(lightdoc, OpenWhereMod.none);
-                            SelectionManager.DeselectAll();
-                            LightboxView.SetLightboxDoc(undefined);
-                        }}
-                    />
-                </div>
-                <div className="lightboxView-penBtn">
-                    <Toggle
-                        tooltip="toggle pen annotation"
-                        color={SettingsManager.userColor}
-                        background={Doc.ActiveTool === InkTool.Pen ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
-                        toggleType={ToggleType.BUTTON}
-                        toggleStatus={Doc.ActiveTool === InkTool.Pen}
-                        type={Type.TERT}
-                        icon={<FontAwesomeIcon icon="pen" size="sm" />}
-                        onClick={e => {
-                            e.stopPropagation();
-                            Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen;
-                        }}
-                    />
-                </div>
-                <div className="lightboxView-exploreBtn">
-                    <Toggle
-                        tooltip="toggle explore mode to navigate among documents only"
-                        color={SettingsManager.userColor}
-                        background={DocumentView.ExploreMode ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
-                        toggleType={ToggleType.BUTTON}
-                        type={Type.TERT}
-                        toggleStatus={DocumentView.ExploreMode}
-                        icon={<FontAwesomeIcon icon="globe-americas" size="sm" />}
-                        onClick={action(e => {
-                            e.stopPropagation();
-                            DocumentView.ExploreMode = !DocumentView.ExploreMode;
-                        })}
-                    />
-                </div>
+                <LightboxTourBtn lightboxDoc={this.lightboxDoc} navBtn={this.renderNavBtn} future={this.future} stepInto={this.stepInto} />
+                {toggleBtn('lightboxView-navBtn', 'toggle reading view', this._doc?._layout_fitWidth, 'book-open', 'book', this.toggleFitWidth)}
+                {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-download', '', this.downloadDoc)}
+                {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Pen, 'pen', '', this.togglePen)}
+                {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', DocumentView.ExploreMode, 'globe-americas', '', this.toggleExplore)}
             </div>
         );
     }
 }
 interface LightboxTourBtnProps {
-    navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element;
+    navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: IconProp, display: any, click: () => void, color?: string) => JSX.Element;
     future: () => Opt<Doc[]>;
     stepInto: () => void;
+    lightboxDoc: () => Opt<Doc>;
 }
 @observer
 export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> {
     render() {
-        return this.props.navBtn(
-            '50%',
-            0,
-            0,
-            'chevron-down',
-            () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'),
-            e => {
-                e.stopPropagation();
-                this.props.stepInto();
-            },
-            ''
-        );
+        return this.props.navBtn('50%', 0, 0, 'chevron-down', this.props.lightboxDoc(), this.props.stepInto, '');
     }
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index da5e4f966..f40f1f3e8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -4,7 +4,7 @@ import * as far from '@fortawesome/free-regular-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import 'browndash-components/dist/styles/global.min.css';
-import { action, computed, configure, observable, runInAction } from 'mobx';
+import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import 'normalize.css';
 import * as React from 'react';
@@ -26,7 +26,7 @@ import { RTFMarkup } from '../util/RTFMarkup';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SelectionManager } from '../util/SelectionManager';
 import { ServerStats } from '../util/ServerStats';
-import { ColorScheme, SettingsManager } from '../util/SettingsManager';
+import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -35,6 +35,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { CollectionLinearView } from './collections/collectionLinear';
 import { CollectionMenu } from './collections/CollectionMenu';
+import { TabDocView } from './collections/TabDocView';
 import './collections/TreeView.scss';
 import { ComponentDecorations } from './ComponentDecorations';
 import { ContextMenu } from './ContextMenu';
@@ -62,6 +63,7 @@ import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
 import { MapBox } from './nodes/MapBox/MapBox';
 import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
+import { PresBox } from './nodes/trails';
 import { OverlayView } from './OverlayView';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
@@ -141,6 +143,11 @@ export class MainView extends React.Component {
     mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
 
     componentDidMount() {
+        reaction(
+            // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
+            () => SelectionManager.Views().slice(),
+            views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur()
+        );
         const scriptTag = document.createElement('script');
         scriptTag.setAttribute('type', 'text/javascript');
         scriptTag.setAttribute('src', 'https://www.bing.com/api/maps/mapcontrol?callback=makeMap');
@@ -194,6 +201,10 @@ export class MainView extends React.Component {
         tag.src = 'https://www.youtube.com/iframe_api';
         const firstScriptTag = document.getElementsByTagName('script')[0];
         firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag);
+        window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+        window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true);
+        window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers);
+        window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers);
         window.removeEventListener('keydown', KeyManager.Instance.handle);
         window.addEventListener('keydown', KeyManager.Instance.handle);
         window.removeEventListener('keyup', KeyManager.Instance.unhandle);
@@ -250,6 +261,7 @@ export class MainView extends React.Component {
                 fa.faShare,
                 fa.faTaxi,
                 fa.faDownload,
+                fa.faPallet,
                 fa.faExpandArrowsAlt,
                 fa.faAmbulance,
                 fa.faLayerGroup,
@@ -261,6 +273,8 @@ export class MainView extends React.Component {
                 fa.faWindowRestore,
                 fa.faFolder,
                 fa.faFolderOpen,
+                fa.faFolderPlus,
+                fa.faFolderClosed,
                 fa.faMapPin,
                 fa.faMapMarker,
                 fa.faFingerprint,
@@ -450,6 +464,7 @@ export class MainView extends React.Component {
                 fa.faSortUp,
                 fa.faSortDown,
                 fa.faTable,
+                fa.faTableCells,
                 fa.faTableColumns,
                 fa.faTh,
                 fa.faThList,
@@ -475,11 +490,11 @@ export class MainView extends React.Component {
                 fa.faBookmark,
                 fa.faList,
                 fa.faListOl,
-                fa.faFolderPlus,
                 fa.faLightbulb,
                 fa.faBookOpen,
                 fa.faMapMarkerAlt,
                 fa.faSearchPlus,
+                fa.faSolarPanel,
                 fa.faVolumeUp,
                 fa.faVolumeDown,
                 fa.faSquareRootAlt,
@@ -560,7 +575,7 @@ export class MainView extends React.Component {
     @action
     openPresentation = (pres: Doc) => {
         if (pres.type === DocumentType.PRES) {
-            CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
+            CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName);
             Doc.MyTrails && (Doc.ActivePresentation = pres);
             Doc.AddDocToList(Doc.MyTrails, 'data', pres);
             this.closeFlyout();
@@ -693,16 +708,17 @@ export class MainView extends React.Component {
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
     static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
         const whereFields = location.split(':');
-        const keyValue = whereFields[1]?.includes('KeyValue');
-        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
+        const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
+        const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+        const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
         if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
-        // prettier-ignore
         switch (whereFields[0]) {
-            case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
+            case OpenWhere.lightbox:   return LightboxView.Instance.AddDocTab(doc, location);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
-            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, "dontSelectOnActivate"); // bcz: hack!  mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, undefined, TabDocView.DontSelectOnActivate); // bcz: hack!  mark the toggle so that it won't be selected on activation- this is needed so that the backlinks menu can toggle views of targets on and off without selecting them
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, undefined, panelName);
             case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, undefined, undefined, keyValue);
-        }
+        } // prettier-ignore
     };
 
     @computed get flyout() {
@@ -920,7 +936,7 @@ export class MainView extends React.Component {
         SnappingManager.GetIsDragging();
         SnappingManager.GetIsResizing();
         const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs().lastElement();
-        const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined;
+        const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.CollectionFreeFormView : undefined;
         return !dragPar?.rootDoc.freeform_snapLines ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 0987b0867..20c7a08fa 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,4 +1,4 @@
-import { action, observable, ObservableMap, runInAction } from 'mobx';
+import { action, computed, observable, ObservableMap, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
@@ -21,13 +21,13 @@ const _global = (window /* browser */ || global) /* node */ as any;
 export interface MarqueeAnnotatorProps {
     rootDoc: Doc;
     down?: number[];
-    iframe?: () => undefined | HTMLIFrameElement;
     scrollTop: number;
     scaling?: () => number;
-    iframeScaling?: () => number;
+    annotationLayerScaling?: () => number;
+    annotationLayerScrollTop: number;
     containerOffset?: () => number[];
     mainCont: HTMLDivElement;
-    docView: DocumentView;
+    docView: () => DocumentView;
     savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
     selectionText: () => string;
     annotationLayer: HTMLDivElement;
@@ -40,27 +40,11 @@ export interface MarqueeAnnotatorProps {
 }
 @observer
 export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
-    private _startX: number = 0;
-    private _startY: number = 0;
-    @observable private _left: number = 0;
-    @observable private _top: number = 0;
+    private _start: { x: number; y: number } = { x: 0, y: 0 };
     @observable private _width: number = 0;
     @observable private _height: number = 0;
-
-    constructor(props: any) {
-        super(props);
-
-        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
-            if (this.props.anchorMenuCrop) {
-                UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
-            }
-        };
-        AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
-        AnchorMenu.Instance.OnAudio = unimplementedFunction;
-        AnchorMenu.Instance.Highlight = this.highlight;
-        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
-        AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
-    }
+    @computed get top() { return Math.min(this._start.y, this._start.y + this._height); } // prettier-ignore
+    @computed get left() { return Math.min(this._start.x, this._start.x + this._width);} // prettier-ignore
 
     @action
     static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
@@ -71,81 +55,15 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         savedAnnotations.clear();
     }
 
-    @action gotDownPoint() {
-        if (!this._width && !this._height) {
-            const downPt = this.props.down!;
-            // set marquee x and y positions to the spatially transformed position
-            const boundingRect = this.props.mainCont.getBoundingClientRect();
-            this._startX = this._left = (downPt[0] - boundingRect.left) * (this.props.mainCont.offsetWidth / boundingRect.width);
-            this._startY = this._top = (downPt[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop;
-        }
-
-        const doc = this.props.iframe?.()?.contentDocument ?? document;
-        doc.removeEventListener('pointermove', this.onSelectMove);
-        doc.removeEventListener('pointerup', this.onSelectEnd);
-        doc.addEventListener('pointermove', this.onSelectMove);
-        doc.addEventListener('pointerup', this.onSelectEnd);
-
-        /**
-         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
-         * It also initiates a Drag/Drop interaction to place the text annotation.
-         */
-        AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
-            e.preventDefault();
-            e.stopPropagation();
-            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
-
-            const targetCreator = (annotationOn: Doc | undefined) => {
-                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-                FormattedTextBox.SelectOnLoad = target[Id];
-                return target;
-            };
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                dragComplete: e => {
-                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
-                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
-                    }
-                },
-            });
-        });
-        /**
-         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
-         * It also initiates a Drag/Drop interaction to place the text annotation.
-         */
-        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop
-            ? unimplementedFunction
-            : action((e: PointerEvent, ele: HTMLElement) => {
-                  e.preventDefault();
-                  e.stopPropagation();
-                  var cropRegion: Doc | undefined;
-                  const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
-                  const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
-                  DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
-                      dragComplete: e => {
-                          if (!e.aborted && e.linkDocument) {
-                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title;
-                              Doc.GetProto(e.linkDocument).link_displayLine = false;
-                          }
-                      },
-                  });
-              });
-    }
-    releaseDownPt() {
-        const doc = this.props.iframe?.()?.contentDocument ?? document;
-        doc.removeEventListener('pointermove', this.onSelectMove);
-        doc.removeEventListener('pointerup', this.onSelectEnd);
-    }
-
     @undoBatch
     @action
     makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
         const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
         if (savedAnnoMap.size === 0) return undefined;
         const savedAnnos = Array.from(savedAnnoMap.values())[0];
+        const doc = this.props.docView().rootDoc;
+        const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
         if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
-            const scale = this.props.scaling?.() || 1;
             const anno = savedAnnos[0];
             const containerOffset = this.props.containerOffset?.() || [0, 0];
             const marqueeAnno = Docs.Create.FreeformDocument([], {
@@ -154,10 +72,10 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                 annotationOn: this.props.rootDoc,
                 title: 'Annotation on ' + this.props.rootDoc.title,
             });
-            marqueeAnno.x = NumCast(this.props.docView.props.Document.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
-            marqueeAnno.y = NumCast(this.props.docView.props.Document.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1) + NumCast(this.props.scrollTop);
-            marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
-            marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._freeform_scale, 1);
+            marqueeAnno.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale;
+            marqueeAnno.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale;
+            marqueeAnno._height = parseInt(anno.style.height || '0') / scale;
+            marqueeAnno._width = parseInt(anno.style.width || '0') / scale;
             anno.remove();
             savedAnnoMap.clear();
             return marqueeAnno;
@@ -210,88 +128,148 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         // creates annotation documents for current highlights
         const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]);
         const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
-        addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc);
+        addAsAnnotation && annotationDoc && this.props.addDocument(annotationDoc);
         return annotationDoc as Doc;
     };
 
     public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
-        if (div.style.top) {
-            div.style.top = parseInt(div.style.top) /*+ this.getScrollFromPage(page)*/
-                .toString();
-        }
-        annotationLayer.append(div);
         div.style.backgroundColor = '#ACCEF7';
         div.style.opacity = '0.5';
+        annotationLayer.append(div);
         const savedPage = savedAnnotations.get(page);
-        if (savedPage) {
-            savedPage.push(div);
-            savedAnnotations.set(page, savedPage);
-        } else {
-            savedAnnotations.set(page, [div]);
-        }
+        if (savedPage) savedPage.push(div);
+        savedAnnotations.set(page, savedPage ?? [div]);
     });
 
+    getTransformedScreenPt = (down: number[]) => {
+        const boundingRect = this.props.mainCont.getBoundingClientRect();
+        const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
+        const downPt = Utils.rotPt(down[0] - center.x, down[1] - center.y, NumCast(this.props.docView().screenToLocalTransform().Rotate));
+        const scale = this.props.docView().props.ScreenToLocalTransform().Scale;
+        const scalex = this.props.mainCont.offsetWidth / NumCast(this.props.rootDoc.width);
+        const scaley = this.props.mainCont.offsetHeight / NumCast(this.props.rootDoc.height);
+        // set marquee x and y positions to the spatially transformed position
+        return { x: scalex * (downPt.x + NumCast(this.props.rootDoc.width) / scale / 2) * scale, 
+                 y: scaley * (downPt.y + NumCast(this.props.rootDoc.height) / scale / 2) * scale + this.props.annotationLayerScrollTop }; // prettier-ignore
+    };
+
+    @action
+    public onInitiateSelection(down: number[]) {
+        this._width = this._height = 0;
+        this._start = this.getTransformedScreenPt(down);
+
+        document.removeEventListener('pointermove', this.onSelectMove);
+        document.removeEventListener('pointerup', this.onSelectEnd);
+        document.addEventListener('pointermove', this.onSelectMove);
+        document.addEventListener('pointerup', this.onSelectEnd);
+
+        AnchorMenu.Instance.OnCrop = (e: PointerEvent) => {
+            if (this.props.anchorMenuCrop) {
+                UndoManager.RunInBatch(() => this.props.anchorMenuCrop?.(this.highlight('', true, undefined, false), true), 'cropping');
+            }
+        };
+        AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
+        AnchorMenu.Instance.OnAudio = unimplementedFunction;
+        AnchorMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
+        AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
+
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color
+
+            const targetCreator = (annotationOn: Doc | undefined) => {
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+                FormattedTextBox.SelectOnLoad = target[Id];
+                return target;
+            };
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                dragComplete: e => {
+                    if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+                        e.annoDragData.linkSourceDoc.followLinkZoom = false;
+                    }
+                },
+            });
+        });
+        /**
+         * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation.
+         * It also initiates a Drag/Drop interaction to place the text annotation.
+         */
+        AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop
+            ? unimplementedFunction
+            : action((e: PointerEvent, ele: HTMLElement) => {
+                  e.preventDefault();
+                  e.stopPropagation();
+                  var cropRegion: Doc | undefined;
+                  const sourceAnchorCreator = () => (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color
+                  const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!;
+                  DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
+                      dragComplete: e => {
+                          if (!e.aborted && e.linkDocument) {
+                              Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
+                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().rootDoc.title;
+                              Doc.GetProto(e.linkDocument).link_displayLine = false;
+                          }
+                      },
+                  });
+              });
+    }
+    public onTerminateSelection() {
+        document.removeEventListener('pointermove', this.onSelectMove);
+        document.removeEventListener('pointerup', this.onSelectEnd);
+    }
+
     @action
     onSelectMove = (e: PointerEvent) => {
-        // transform positions and find the width and height to set the marquee to
-        const boundingRect = (this.props.iframe?.()?.contentDocument?.body || this.props.mainCont).getBoundingClientRect();
-        const mainRect = this.props.mainCont.getBoundingClientRect();
-        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left;
-        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top;
-        this._width = cliX * (this.props.mainCont.offsetWidth / mainRect.width) - this._startX;
-        this._height = cliY * (this.props.mainCont.offsetHeight / mainRect.height) - this._startY + this.props.mainCont.scrollTop;
-        this._left = Math.min(this._startX, this._startX + this._width);
-        this._top = Math.min(this._startY, this._startY + this._height);
-        this._width = Math.abs(this._width);
-        this._height = Math.abs(this._height);
+        const movLoc = this.getTransformedScreenPt([e.clientX, e.clientY]);
+        this._width = movLoc.x - this._start.x;
+        this._height = movLoc.y - this._start.y;
         //e.stopPropagation(); // overlay documents are all 'active', yet they can be dragged. if we stop propagation, then they can be marqueed but not dragged.  if we don't stop, then they will be marqueed and dragged, but the marquee will be zero width since the doc will move along with the cursor.
     };
 
+    @action
     onSelectEnd = (e: PointerEvent) => {
-        const mainRect = this.props.mainCont.getBoundingClientRect();
-        const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0);
-        const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0);
-        if (this._width > 10 || this._height > 10) {
+        e.stopPropagation();
+        const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox');
+        const marqueeStyle = (Array.from(marquees).lastElement() as HTMLDivElement)?.style;
+        if (!this.isEmpty && marqueeStyle) {
             // configure and show the annotation/link menu if a the drag region is big enough
-            const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox');
-            if (marquees?.length) {
-                // copy the temporary marquee to allow for multiple selections (not currently available though).
-                const copy = document.createElement('div');
-                ['border', 'opacity'].forEach(prop => (copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]));
-                const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect();
-                const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement);
-                const rbounds = { top: uitls.translateY, left: uitls.translateX, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top };
-                const otls = Utils.GetScreenTransform(this.props.annotationLayer);
-                const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale };
-                copy.style.top = fbounds.top.toString() + 'px';
-                copy.style.left = fbounds.left.toString() + 'px';
-                copy.style.width = fbounds.width.toString() + 'px';
-                copy.style.height = fbounds.height.toString() + 'px';
-                copy.className = 'marqueeAnnotator-annotationBox';
-                (copy as any).marqueeing = true;
-                MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0);
-            }
-
-            AnchorMenu.Instance.jumpTo(cliX, cliY);
-
-            this.props.finishMarquee(undefined, undefined, e);
-            runInAction(() => (this._width = this._height = 0));
-        } else {
-            runInAction(() => (this._width = this._height = 0));
-            this.props.finishMarquee(cliX, cliY, e);
+            // copy the temporary marquee to allow for multiple selections (not currently available though).
+            const copy = document.createElement('div');
+            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().rootDoc._freeform_scale, 1);
+            ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
+            copy.className = 'marqueeAnnotator-annotationBox';
+            copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
+            copy.style.left = parseInt(marqueeStyle.left.toString().replace('px', '')) / scale + 'px';
+            copy.style.width = parseInt(marqueeStyle.width.toString().replace('px', '')) / scale + 'px';
+            copy.style.height = parseInt(marqueeStyle.height.toString().replace('px', '')) / scale + 'px';
+            (copy as any).marqueeing = true;
+            MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this.top) || 0);
+            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
         }
+        this.props.finishMarquee(this.isEmpty ? e.clientX : undefined, this.isEmpty ? e.clientY : undefined, e);
+        this._width = this._height = 0;
     };
 
+    get isEmpty() {
+        return Math.abs(this._width) <= 10 && Math.abs(this._height) <= 10;
+    }
+
     render() {
-        return !this.props.down ? null : (
+        return (
             <div
-                ref={r => (r ? this.gotDownPoint() : this.releaseDownPt())}
                 className="marqueeAnnotator-dragBox"
                 style={{
-                    left: `${this._left}px`,
-                    top: `${this._top}px`,
-                    width: `${this._width}px`,
-                    height: `${this._height}px`,
+                    left: `${this.left}px`,
+                    top: `${this.top}px`,
+                    width: `${Math.abs(this._width)}px`,
+                    height: `${Math.abs(this._height)}px`,
                     border: `${this._width === 0 ? '' : '2px dashed black'}`,
                 }}
             />
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index c174befc0..c7b59a8e2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Doc } from '../../fields/Doc';
 import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { NumCast } from '../../fields/Types';
@@ -119,7 +119,7 @@ export class OverlayView extends React.Component {
             new _global.ResizeObserver(
                 action((entries: any) => {
                     for (const entry of entries) {
-                        DocListCast(Doc.MyOverlayDocs?.data).forEach(doc => {
+                        Doc.MyOverlayDocs.forEach(doc => {
                             if (NumCast(doc.overlayX) > entry.contentRect.width - 10) {
                                 doc.overlayX = entry.contentRect.width - 10;
                             }
@@ -184,70 +184,68 @@ export class OverlayView extends React.Component {
     );
 
     @computed get overlayDocs() {
-        return DocListCast(Doc.MyOverlayDocs?.data)
-            .filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES)
-            .map(d => {
-                let offsetx = 0,
-                    offsety = 0;
-                const dref = React.createRef<HTMLDivElement>();
-                const onPointerMove = action((e: PointerEvent, down: number[]) => {
-                    if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
-                    if (e.buttons === 1) {
-                        d.overlayX = e.clientX + offsetx;
-                        d.overlayY = e.clientY + offsety;
-                    }
-                    if (e.metaKey) {
-                        const dragData = new DragManager.DocumentDragData([d]);
-                        dragData.offset = [-offsetx, -offsety];
-                        dragData.dropAction = 'move';
-                        dragData.removeDocument = this.removeOverlayDoc;
-                        dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-                            return dragData.removeDocument!(doc) ? addDocument(doc) : false;
-                        };
-                        DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
-                        return true;
-                    }
-                    return false;
-                });
-
-                const onPointerDown = (e: React.PointerEvent) => {
-                    setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
-                    offsetx = NumCast(d.overlayX) - e.clientX;
-                    offsety = NumCast(d.overlayY) - e.clientY;
-                };
-                return (
-                    <div
-                        className="overlayView-doc"
-                        ref={dref}
-                        key={d[Id]}
-                        onPointerDown={onPointerDown}
-                        style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
-                        <DocumentView
-                            Document={d}
-                            rootSelected={returnTrue}
-                            bringToFront={emptyFunction}
-                            addDocument={undefined}
-                            removeDocument={this.removeOverlayDoc}
-                            PanelWidth={d[Width]}
-                            PanelHeight={d[Height]}
-                            ScreenToLocalTransform={this.docScreenToLocalXf(d)}
-                            renderDepth={1}
-                            hideDecorations={true}
-                            isDocumentActive={returnTrue}
-                            isContentActive={returnTrue}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            focus={emptyFunction}
-                            styleProvider={DefaultStyleProvider}
-                            docViewPath={returnEmptyDoclist}
-                            addDocTab={DocumentViewInternal.addDocTabFunc}
-                            pinToPres={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                    </div>
-                );
+        return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => {
+            let offsetx = 0,
+                offsety = 0;
+            const dref = React.createRef<HTMLDivElement>();
+            const onPointerMove = action((e: PointerEvent, down: number[]) => {
+                if (e.cancelBubble) return false; // if the overlay doc processed the move event (e.g., to pan its contents), then the event should be marked as canceled since propagation can't be stopped
+                if (e.buttons === 1) {
+                    d.overlayX = e.clientX + offsetx;
+                    d.overlayY = e.clientY + offsety;
+                }
+                if (e.metaKey) {
+                    const dragData = new DragManager.DocumentDragData([d]);
+                    dragData.offset = [-offsetx, -offsety];
+                    dragData.dropAction = 'move';
+                    dragData.removeDocument = this.removeOverlayDoc;
+                    dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+                        return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+                    };
+                    DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+                    return true;
+                }
+                return false;
             });
+
+            const onPointerDown = (e: React.PointerEvent) => {
+                setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction, false);
+                offsetx = NumCast(d.overlayX) - e.clientX;
+                offsety = NumCast(d.overlayY) - e.clientY;
+            };
+            return (
+                <div
+                    className="overlayView-doc"
+                    ref={dref}
+                    key={d[Id]}
+                    onPointerDown={onPointerDown}
+                    style={{ top: d.type === DocumentType.PRES ? 0 : undefined, width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.overlayX}px, ${d.overlayY}px)` }}>
+                    <DocumentView
+                        Document={d}
+                        rootSelected={returnTrue}
+                        bringToFront={emptyFunction}
+                        addDocument={undefined}
+                        removeDocument={this.removeOverlayDoc}
+                        PanelWidth={d[Width]}
+                        PanelHeight={d[Height]}
+                        ScreenToLocalTransform={this.docScreenToLocalXf(d)}
+                        renderDepth={1}
+                        hideDecorations={true}
+                        isDocumentActive={returnTrue}
+                        isContentActive={returnTrue}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        styleProvider={DefaultStyleProvider}
+                        docViewPath={returnEmptyDoclist}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
+                        pinToPres={emptyFunction}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                    />
+                </div>
+            );
+        });
     }
 
     public static ShowSpinner() {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index d37971517..1b9b26111 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,12 +5,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@material-ui/core';
 import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
 import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
 import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
-import { AclAdmin, DocAcl, DocData, Height, Width } from '../../fields/DocSymbols';
+import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -119,20 +119,23 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get isInk() {
         return this.selectedDoc?.type === DocumentType.INK;
     }
+    @computed get isGroup() {
+        return this.selectedDoc?.isGroup;
+    }
     @computed get isStack() {
         return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
     }
 
-    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20));
-    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
+    rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(NumCast(this.selectedDoc?._width), this.props.width - 20));
+    rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedDoc?._width) ? Math.min(NumCast(this.selectedDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
 
     @action
     docWidth = () => {
         if (this.selectedDoc) {
             const layoutDoc = this.selectedDoc;
             const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
-            if (aspect) return Math.min(layoutDoc[Width](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
-            return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[Width](), this.props.width - 20) : this.props.width - 20;
+            if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
+            return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
         }
         return 0;
     };
@@ -1098,6 +1101,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             <div className="transform-editor">
                 {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
                 {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
+                {!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
                 {this.isInk ? this.controlPointsButton : null}
                 {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
                 {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index c6d3efd0c..2162d8878 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -7,13 +7,13 @@ import { extname } from 'path';
 import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
+import { DocViews } from '../../fields/DocSymbols';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
 import { DashColor, lightOrDark, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
-import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
@@ -105,7 +105,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
                 return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
             }
-            return Doc.toIcon(doc, isEmpty ? undefined : isOpen);
+            return Doc.toIcon(doc, isOpen);
         case StyleProp.TreeViewSortings:
             const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
             allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
@@ -116,8 +116,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Highlighting:
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
             if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
-                const selected = SelectionManager.Views().some(dv => dv.rootDoc === doc);
-                const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
+                const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.SELECTED).length;
+                const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
@@ -282,7 +282,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const showFilterIcon =
                     StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                         ? 'green' // #18c718bd' //'hasFilter'
-                        : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || docProps?.childFiltersByRanges().length
+                        : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || docProps?.childFiltersByRanges().length
                         ? 'orange' //'inheritsFilter'
                         : undefined;
                 return !showFilterIcon ? null : (
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index f36b5ade8..d959241d0 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -252,6 +252,9 @@ export class Track extends React.Component<IProps> {
     @action
     private applyKeys = (kf: Doc) => {
         this.primitiveWhitelist.forEach(key => {
+            if (key === 'opacity' && this.props.animatedDoc === this.props.collection) {
+                return;
+            }
             if (!kf[key]) {
                 this.props.animatedDoc[key] = undefined;
             } else {
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index c0530ab81..333ba9f32 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -56,7 +56,7 @@
 // }
 .lm_header .lm_controls {
     position: absolute;
-    right: 3px;
+    right: 0px;
 }
 .lm_header .lm_controls > li {
     cursor: pointer;
@@ -64,16 +64,12 @@
     width: 18px;
     height: 18px;
     text-align: center;
-    top: 3px;
 }
 .lm_header ul {
     margin: 0;
     padding: 0;
     list-style-type: none;
 }
-.lm_header .lm_tabs {
-    position: absolute;
-}
 .lm_header .lm_tab {
     cursor: pointer;
     float: left;
@@ -329,8 +325,9 @@
 }
 
 .lm_header .lm_tabs {
+    position: absolute;
     overflow-y: hidden;
-    width: 100%;
+    width: calc(100% - 5px);
 }
 ul.lm_tabs::before {
     content: ' ';
@@ -484,8 +481,6 @@ ul.lm_tabs::before {
 .collectiondockingview-container {
     width: 100%;
     height: 100%;
-    border-style: solid;
-    border-width: $COLLECTION_BORDER_WIDTH;
     position: absolute;
     top: 0;
     left: 0;
@@ -508,6 +503,7 @@ ul.lm_tabs::before {
         display: flex;
         align-content: center;
         justify-content: center;
+        background: transparent !important;
     }
 
     .lm_controls > li {
@@ -518,7 +514,11 @@ ul.lm_tabs::before {
 
     .lm_controls .lm_popout {
         background-image: unset;
-        left: -3;
+        border-top-left-radius: 10px;
+        border-bottom-left-radius: 10px;
+        background: #93939347;
+        z-index: 100;
+        //left: -3;
         &:hover {
             background: gray;
             color: white !important;
@@ -528,7 +528,7 @@ ul.lm_tabs::before {
         content: '+';
         margin: auto;
         font-size: x-large;
-        top: -6;
+        top: -4;
         position: relative;
     }
     .lm_maximise {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4873a61ff..f155e64b5 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -85,6 +85,7 @@ export class CollectionDockingView extends CollectionSubView() {
         const dragSource = CollectionDockingView.Instance?._goldenLayout.createDragSource(document.createElement('div'), config);
         this.tabDragStart(dragSource, finishDrag);
         dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 });
+        return true;
     };
 
     tabItemDropped = () => DragManager.CompleteWindowDrag?.(false);
@@ -130,7 +131,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @undoBatch
     @action
-    public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean {
+    public static ReplaceTab(document: Doc, mods: OpenWhereMod, stack: any, panelName: string, addToSplit?: boolean, keyValue?: boolean): boolean {
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
         const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
@@ -151,7 +152,7 @@ export class CollectionDockingView extends CollectionSubView() {
             }
             return false;
         }
-        return CollectionDockingView.AddSplit(document, panelName, stack, panelName);
+        return CollectionDockingView.AddSplit(document, mods, stack, panelName);
     }
 
     @undoBatch
@@ -406,8 +407,9 @@ export class CollectionDockingView extends CollectionSubView() {
             window.addEventListener('mouseup', this.onPointerUp);
             if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) {
                 const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : '';
-                if (className.includes('lm_maximise')) this._flush = UndoManager.StartBatch('tab maximize');
-                else {
+                if (className.includes('lm_maximise')) {
+                    // this._flush = UndoManager.StartBatch('tab maximize');
+                } else {
                     const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement);
                     const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget);
                     if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) {
@@ -589,7 +591,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
 ScriptingGlobals.add(
     function openInLightbox(doc: any) {
-        LightboxView.AddDocTab(doc, OpenWhere.lightbox);
+        LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
     },
     'opens up document in a lightbox',
     '(doc: any)'
@@ -618,6 +620,3 @@ ScriptingGlobals.add(
     'opens up document in screen overlay layer',
     '(doc: any)'
 );
-ScriptingGlobals.add(function useRightSplit(doc: any, addToRightSplit?: boolean) {
-    CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, addToRightSplit);
-});
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 52cf40635..cf154be8d 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -40,8 +40,6 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
-import { COLLECTION_BORDER_WIDTH } from './CollectionView';
-import { TabDocView } from './TabDocView';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
@@ -557,25 +555,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView'));
         } else return false;
     }
-    @computed
-    get pinButton() {
-        const targetDoc = this.selectedDoc;
-        const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
-        return !targetDoc ? null : (
-            <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? 'Unpin from presentation' : 'Pin to presentation'}</div>} placement="top">
-                <button
-                    className="antimodeMenu-button"
-                    style={{ backgroundColor: isPinned ? '121212' : undefined, borderLeft: '1px solid gray' }}
-                    onClick={e =>
-                        TabDocView.PinDoc(targetDoc, {
-                            /* unpin: isPinned*/
-                        })
-                    }>
-                    <FontAwesomeIcon className="colMenu-icon" size="lg" icon="map-pin" />
-                </button>
-            </Tooltip>
-        );
-    }
 
     @undoBatch
     @action
@@ -653,7 +632,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                     className="antimodeMenu-button"
                     onPointerDown={() => {
                         const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
-                        LightboxView.SetLightboxDoc(targetDoc, undefined, docs);
+                        LightboxView.Instance.SetLightboxDoc(targetDoc, undefined, docs);
                     }}>
                     <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" />
                 </button>
@@ -673,7 +652,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                             pointerEvents: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'none' : undefined,
                             color: this.props.docView.props.docViewPath().lastElement()?.rootDoc?._type_collection !== CollectionViewType.Freeform ? 'dimgrey' : undefined,
                         }}
-                        onClick={undoBatch(() => this.props.docView.props.CollectionFreeFormDocumentView?.().float())}>
+                        onClick={undoBatch(() => this.props.docView.CollectionFreeFormDocumentView?.float())}>
                         <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
                     </button>
                 </Tooltip>
@@ -1261,7 +1240,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
     @undoBatch
     togglePreview = () => {
         const dividerWidth = 4;
-        const borderWidth = Number(COLLECTION_BORDER_WIDTH);
+        const borderWidth = 0;
         const panelWidth = this.props.docView.props.PanelWidth();
         const previewWidth = NumCast(this.document.schema_previewWidth);
         const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index afeef5a8f..ac916fef3 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { CursorProperty } from 'csstype';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
@@ -289,7 +289,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const existingHeader = this.colHeaderData.find(sh => sh.heading === heading);
         const existingWidth = existingHeader?.width ? existingHeader.width : 0;
         const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth;
-        const width = d.layout_fitWidth ? maxWidth : d[Width]();
+        const width = d.layout_fitWidth ? maxWidth : NumCast(d._width);
         return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth);
     }
 
@@ -299,8 +299,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const docWid = this.getDocWidth(d);
             return Math.min(maxHeight, (docWid * nh) / nw);
@@ -336,7 +336,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // onPointerMove is used to preview where a document will drop in a column once a drag is complete.
     @action
     onPointerMove = (force: boolean, ex: number, ey: number) => {
-        if (this.childDocList && (this.childDocList.includes(DragManager.DocDragData?.draggedDocuments.lastElement()!) || force || this.isContentActive())) {
+        if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || this.isContentActive()) {
             // get the current docs for the column based on the mouse's x coordinate
             const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
             const colDocs = this.getDocsFromXCoord(xCoord);
@@ -443,7 +443,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this.props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 91701b213..abb12a8ab 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,7 +1,6 @@
 import { action, computed, IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -79,12 +78,12 @@ export class CollectionPileView extends CollectionSubView() {
     toggleStarburst = action(() => {
         this.layoutDoc._freeform_scale = undefined;
         if (this.layoutEngine() === computeStarburstLayout.name) {
-            if (this.rootDoc[Width]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
-                this.rootDoc._starburstDiameter = this.rootDoc[Width]();
+            if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+                this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width);
             }
             const defaultSize = 110;
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
             this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
             this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
@@ -93,10 +92,10 @@ export class CollectionPileView extends CollectionSubView() {
             this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
-            this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[Width]() / 2 - defaultSize / 2;
-            this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[Height]() / 2 - defaultSize / 2;
-            this.layoutDoc._freeform_pileWidth = this.layoutDoc[Width]();
-            this.layoutDoc._freeform_pileHeight = this.layoutDoc[Height]();
+            this.rootDoc.x = NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+            this.rootDoc.y = NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
+            this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
+            this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.layoutDoc.background;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 7c61bc4da..3351ca48e 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -270,7 +270,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         !wasPlaying && doubleTap && this.props.Play();
                     }
                 },
-                this.props.isSelected(true) || this.props.isContentActive(),
+                this.props.isSelected() || this.props.isContentActive(),
                 undefined,
                 () => {
                     if (shiftKey) {
@@ -681,7 +681,7 @@ interface StackedTimelineAnchorProps {
     _timeline: HTMLDivElement | null;
     focus: DocFocusFunc;
     currentTimecode: () => number;
-    isSelected: (outsideReaction?: boolean) => boolean;
+    isSelected: () => boolean;
     stackedTimeline: CollectionStackedTimeline;
     trimStart: number;
     trimEnd: number;
@@ -784,7 +784,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
 
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
-        const anchor = observable({ view: undefined as any });
+        const anchor = observable({ view: undefined as Opt<DocumentView> | null });
         const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
             this.props.playLink(mark, options);
             return undefined;
@@ -838,7 +838,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
         return (
             <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
                 {inner.view}
-                {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+                {!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
                     <>
                         <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
                         <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0b29e7286..da00093dd 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -4,17 +4,18 @@ import { CursorProperty } from 'csstype';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -31,7 +32,6 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
 import { CollectionSubView } from './CollectionSubView';
-import { SettingsManager } from '../../util/SettingsManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -378,7 +378,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d))) {
-            return Math.min(d[Width](), maxWidth);
+            return Math.min(NumCast(d._width), maxWidth);
         }
         return maxWidth;
     }
@@ -387,8 +387,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.DataDoc;
         const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Width]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? d[Height]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
         if (nw && nh) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
@@ -471,7 +471,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
             if (!this.props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
diff --git a/src/client/views/collections/CollectionStaffView.scss b/src/client/views/collections/CollectionStaffView.scss
deleted file mode 100644
index 493a5f670..000000000
--- a/src/client/views/collections/CollectionStaffView.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.collectionStaffView {
-    .collectionStaffView-staff {
-        width: 100%;
-        margin-top: 100px;
-        margin-bottom: 100px;
-    }
-
-    .collectionStaffView-line {
-        margin: 10px;
-        height: 2px;
-        background: black;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
deleted file mode 100644
index c025e94a8..000000000
--- a/src/client/views/collections/CollectionStaffView.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { CollectionSubView } from "./CollectionSubView";
-import React = require("react");
-import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
-import { NumCast } from "../../../fields/Types";
-import "./CollectionStaffView.scss";
-import { observer } from "mobx-react";
-
-@observer
-export class CollectionStaffView extends CollectionSubView() {
-    private _reactionDisposer: IReactionDisposer | undefined;
-    @observable private _staves = NumCast(this.props.Document.staves);
-
-    componentWillUnmount() {
-        this._reactionDisposer?.();
-    }
-    componentDidMount = () => {
-        this._reactionDisposer = reaction(() => NumCast(this.props.Document.staves),
-            (staves) => runInAction(() => this._staves = staves)
-        );
-
-        this.props.Document.staves = 5;
-    }
-
-    @computed get addStaffButton() {
-        return <div onPointerDown={this.addStaff}>+</div>;
-    }
-
-    @computed get staves() {
-        const staves = [];
-        for (let i = 0; i < this._staves; i++) {
-            const rows = [];
-            for (let j = 0; j < 5; j++) {
-                rows.push(<div key={`staff-${i}-${j}`} className="collectionStaffView-line"></div>);
-            }
-            staves.push(<div key={`staff-${i}`} className="collectionStaffView-staff">
-                {rows}
-            </div>);
-        }
-        return staves;
-    }
-
-    @action
-    addStaff = (e: React.PointerEvent) => {
-        this.props.Document.staves = this._staves + 1;
-    }
-
-    render() {
-        return <div className="collectionStaffView">
-            {this.staves}
-            {this.addStaffButton}
-        </div>;
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8a1ba0df1..328b060c4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -25,6 +25,7 @@ import { DocComponent } from '../DocComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
 import React = require('react');
+import { LoadingBox } from '../nodes/LoadingBox';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -34,19 +35,16 @@ export function CollectionSubView<X>(moreProps?: X) {
     class CollectionSubView extends DocComponent<X & SubCollectionViewProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
-        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         protected _mainCont?: HTMLDivElement;
         @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
         @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
         protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
             this.dropDisposer?.();
             this.gestureDisposer?.();
-            this._multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
-                this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
         };
         protected CreateDropTarget(ele: HTMLDivElement) {
@@ -56,16 +54,13 @@ export function CollectionSubView<X>(moreProps?: X) {
 
         componentWillUnmount() {
             this.gestureDisposer?.();
-            this._multiTouchDisposer?.();
         }
 
         @computed get dataDoc() {
             return this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
-        rootSelected = (outsideReaction?: boolean) => {
-            return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction));
-        };
+        rootSelected = () => this.props.isSelected() || (this.rootDoc && this.props.rootSelected());
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
         // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through
@@ -125,7 +120,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             const docsforFilter: Doc[] = [];
             childDocs.forEach(d => {
                 // dragging facets
-                const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragsDocFilter));
+                const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
                 if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false;
                 let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
                 if (notFiltered) {
@@ -450,13 +445,13 @@ export function CollectionSubView<X>(moreProps?: X) {
             if (typeof files === 'string') {
                 const loading = Docs.Create.LoadingDocument(files, options);
                 generatedDocuments.push(loading);
-                Doc.addCurrentlyLoading(loading);
+                LoadingBox.addCurrentlyLoading(loading);
                 DocUtils.uploadYoutubeVideoLoading(files, {}, loading);
             } else {
                 generatedDocuments.push(
                     ...files.map(file => {
                         const loading = Docs.Create.LoadingDocument(file, options);
-                        Doc.addCurrentlyLoading(loading);
+                        LoadingBox.addCurrentlyLoading(loading);
                         DocUtils.uploadFileToDoc(file, {}, loading);
                         return loading;
                     })
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2bf649caf..21efeba44 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -4,7 +4,6 @@
     transform-origin: top left;
 }
 .collectionTreeView-dropTarget {
-    border-width: $COLLECTION_BORDER_WIDTH;
     border-color: transparent;
     border-style: solid;
     border-radius: inherit;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e408c193a..761192a22 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -371,8 +371,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     marginX = () => NumCast(this.doc._xMargin);
     marginTop = () => NumCast(this.doc._yMargin);
     marginBot = () => NumCast(this.doc._yMargin);
-    documentTitleWidth = () => Math.min(this.layoutDoc?.[Width](), this.panelWidth());
-    documentTitleHeight = () => (this.layoutDoc?.[Height]() || 0) - NumCast(this.layoutDoc.layout_autoHeightMargins);
+    documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
+    documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
     panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c2062e8ab..389a9a534 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -33,7 +33,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
-export const COLLECTION_BORDER_WIDTH = 2;
 const path = require('path');
 
 interface CollectionViewProps_ extends FieldViewProps {
@@ -81,8 +80,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     private reactionDisposer: IReactionDisposer | undefined;
     @observable _isContentActive: boolean | undefined;
 
-    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
     constructor(props: any) {
         super(props);
         runInAction(() => (this._annotationKeySuffix = returnEmptyString));
@@ -124,23 +121,22 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         if (type === undefined) return null;
         switch (type) {
             default:
-            case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
-            case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
-            case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
-            case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
-            case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />;
-            case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />;
-            case CollectionViewType.Multirow: return <CollectionMultirowView key="collview" {...props} />;
-            case CollectionViewType.Linear: return <CollectionLinearView key="collview" {...props} />;
-            case CollectionViewType.Pile: return <CollectionPileView key="collview" {...props} />;
-            case CollectionViewType.Carousel: return <CollectionCarouselView key="collview" {...props} />;
-            case CollectionViewType.Carousel3D: return <CollectionCarousel3DView key="collview" {...props} />;
-            case CollectionViewType.Stacking: return <CollectionStackingView key="collview" {...props} />;
-            case CollectionViewType.NoteTaking: return <CollectionNoteTakingView key="collview" {...props} />;
-            case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />;
-            case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />;
-            case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />;
-            //case CollectionViewType.Staff: return <CollectionStaffView key="collview" {...props} />;
+            case CollectionViewType.Freeform:     return <CollectionFreeFormView    key="collview" {...props} />;
+            case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
+            case CollectionViewType.Schema:       return <CollectionSchemaView      key="collview" {...props} />;
+            case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
+            case CollectionViewType.Tree:         return <CollectionTreeView        key="collview" {...props} />;
+            case CollectionViewType.Multicolumn:  return <CollectionMulticolumnView key="collview" {...props} />;
+            case CollectionViewType.Multirow:     return <CollectionMultirowView    key="collview" {...props} />;
+            case CollectionViewType.Linear:       return <CollectionLinearView      key="collview" {...props} />;
+            case CollectionViewType.Pile:         return <CollectionPileView        key="collview" {...props} />;
+            case CollectionViewType.Carousel:     return <CollectionCarouselView    key="collview" {...props} />;
+            case CollectionViewType.Carousel3D:   return <CollectionCarousel3DView  key="collview" {...props} />;
+            case CollectionViewType.Stacking:     return <CollectionStackingView    key="collview" {...props} />;
+            case CollectionViewType.NoteTaking:   return <CollectionNoteTakingView  key="collview" {...props} />;
+            case CollectionViewType.Masonry:      return <CollectionStackingView    key="collview" {...props} />;
+            case CollectionViewType.Time:         return <CollectionTimeView        key="collview" {...props} />;
+            case CollectionViewType.Grid:         return <CollectionGridView        key="collview" {...props} />;
         }
     };
 
@@ -175,7 +171,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         if (cm && !e.isPropagationStopped()) {
             // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             !Doc.noviceMode &&
-                this.setupViewTypes('UI Controls...', vtype => {
+                this.setupViewTypes('Appearance...', vtype => {
                     const newRendition = Doc.MakeEmbedding(this.rootDoc);
                     newRendition._type_collection = vtype;
                     this.props.addDocTab(newRendition, OpenWhere.addRight);
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index 13bb3a577..d447991a1 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -15,7 +15,6 @@ input.lm_title {
 }
 
 input.lm_title {
-    transition-delay: 0.35s;
     width: max-content;
     cursor: pointer;
 }
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 26aa5a121..6e1e6cf8d 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -6,12 +6,12 @@ import { action, computed, IReactionDisposer, observable, ObservableSet, reactio
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc, Opt } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
 import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -53,7 +53,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     @observable _isActive: boolean = false;
     @observable _isAnyChildContentActive = false;
     @computed get _isUserActivated() {
-        return SelectionManager.Views().some(view => view.rootDoc === this._document) || this._isAnyChildContentActive;
+        return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
     }
     @computed get _isContentActive() {
         return this._isUserActivated || this._hovering;
@@ -64,24 +64,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     @computed get layoutDoc() {
         return this._document && Doc.Layout(this._document);
     }
-    @computed get tabBorderColor() {
-        const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting);
-        if (highlight?.highlightIndex === Doc.DocBrushStatus.highlighted) return highlight.highlightColor;
-        return 'transparent';
-    }
-    @computed get tabColor() {
-        return this._isUserActivated ? Colors.WHITE : this._hovering ? Colors.LIGHT_GRAY : Colors.MEDIUM_GRAY;
-    }
-    @computed get tabTextColor() {
-        return this._document?.type === DocumentType.PRES ? 'black' : StrCast(this._document?._color, StrCast(this._document?.color, DefaultStyleProvider(this._document, undefined, StyleProp.Color)));
-    }
-    // @computed get renderBounds() {
-    //     const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
-    //     const xbounds = bounds[2] - bounds[0];
-    //     const ybounds = bounds[3] - bounds[1];
-    //     const dim = Math.max(xbounds, ybounds);
-    //     return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
-    // }
 
     get stack() {
         return (this.props as any).glContainer.parent.parent;
@@ -163,14 +145,31 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 tab.reactComponents = [iconWrap, closeWrap];
                 tab.element[0].prepend(iconWrap);
                 tab._disposers.color = reaction(
-                    () => ({ color: this.tabColor, borderColor: this.tabBorderColor }),
-                    coloring => {
-                        const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
+                    () => ({ variant: SettingsManager.userVariantColor, degree: Doc.GetBrushStatus(doc), highlight: DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting) }),
+                    ({ variant, degree, highlight }) => {
+                        const color = highlight?.highlightIndex === Doc.DocBrushStatus.highlighted ? highlight.highlightColor : degree ? ['transparent', variant, variant, 'orange'][degree] : variant;
+
+                        const textColor = color === variant ? SettingsManager.userColor : lightOrDark(color);
                         titleEle.style.color = textColor;
-                        titleEle.style.backgroundColor = coloring.borderColor;
                         iconWrap.style.color = textColor;
                         closeWrap.style.color = textColor;
-                        tab.element[0].style.background = coloring.color;
+                        tab.element[0].style.background =
+                            color === variant
+                                ? DashColor(color)
+                                      .fade(
+                                          this._isUserActivated
+                                              ? 0
+                                              : this._hovering
+                                              ? 0.25
+                                              : degree === Doc.DocBrushStatus.selfBrushed
+                                              ? 0.5
+                                              : degree === Doc.DocBrushStatus.protoBrushed //
+                                              ? 0.7
+                                              : 0.9
+                                      )
+                                      .rgb()
+                                      .toString()
+                                : color;
                     },
                     { fireImmediately: true }
                 );
@@ -181,6 +180,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     tab.header.parent.setActiveContentItem(tab.contentItem);
                     tab.setActive(true);
                 }
+                this._document && Doc.BrushDoc(this._document);
+            };
+            tab.element[0].onmouseleave = (e: MouseEvent) => {
+                this._document && Doc.UnBrushDoc(this._document);
             };
 
             tab.element[0].oncontextmenu = (e: MouseEvent) => {
@@ -203,29 +206,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 }
             });
             tab._disposers.selectionDisposer = reaction(
-                () => SelectionManager.Views().some(view => view.rootDoc === this._document),
+                () => SelectionManager.IsSelected(this._document),
                 action(selected => {
                     if (selected) this._activated = true;
                     const toggle = tab.element[0].children[2].children[0] as HTMLInputElement;
                     if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) {
                         undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')();
                     }
-                    toggle.style.fontWeight = selected ? 'bold' : '';
+                    //toggle.style.fontWeight = selected ? 'bold' : '';
                     // toggle.style.textTransform = selected ? "uppercase" : "";
                 }),
                 { fireImmediately: true }
             );
 
             // highlight the tab when the tab document is brushed in any part of the UI
-            // tab._disposers.reactionDisposer = reaction(
-            //     () => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }),
-            //     ({ title, degree }) => {
-            //         titleEle.value = title;
-            //         titleEle.style.padding = degree ? 0 : 2;
-            //         titleEle.style.border = `${['gray', 'gray', 'gray'][degree]} ${['none', 'dashed', 'solid'][degree]} 2px`;
-            //     },
-            //     { fireImmediately: true }
-            // );
+            tab._disposers.reactionDisposer = reaction(
+                () => doc?.title,
+                title => (titleEle.value = title),
+                { fireImmediately: true }
+            );
 
             // clean up the tab when it is closed
             tab.closeElement
@@ -346,11 +345,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
     }
 
+    // Flag indicating that when a tab is activated, it should not select it's document.
+    // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
+    public static DontSelectOnActivate = 'dontSelectOnActivate';
+
     @action.bound
     private onActiveContentItemChanged(contentItem: any) {
         if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
             this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
-            if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== 'dontSelectOnActivate') setTimeout(() => SelectionManager.SelectView(this._view, false));
+            if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false));
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
     }
@@ -365,8 +368,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     addDocTab = (doc: Doc, location: OpenWhere) => {
         SelectionManager.DeselectAll();
         const whereFields = location.split(':');
-        const keyValue = whereFields[1]?.includes('KeyValue');
-        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
+        const keyValue = whereFields.includes(OpenWhereMod.keyvalue);
+        const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+        const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
         if (doc.dockingConfig && !keyValue) return DashboardView.openDashboard(doc);
         // prettier-ignore
         switch (whereFields[0]) {
@@ -379,10 +383,10 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                                                 return true;
                                             }
                                        }
-                                       return LightboxView.AddDocTab(doc, location);
+                                       return LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
-            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
-            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, "dontSelectOnActivate", keyValue);
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, panelName, undefined, keyValue);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
             case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
         }
     };
@@ -539,7 +543,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                             default:                  return 'gray';
                         }
                     })(doc.type as DocumentType);
-                    return !background ? undefined : <div style={{ width: doc[Width](), height: doc[Height](), position: 'absolute', display: 'block', background }} />;
+                    return !background ? undefined : <div style={{ width: NumCast(doc._width), height: NumCast(doc._height), position: 'absolute', display: 'block', background }} />;
             }
         }
     };
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 193c70add..004857ed1 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,9 +1,10 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton, Size } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
-import { DocData, Height, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -19,6 +20,7 @@ import { DragManager, dropActionType } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
@@ -32,11 +34,9 @@ import { KeyValueBox } from '../nodes/KeyValueBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
 import { CollectionView } from './CollectionView';
+import { TreeSort } from './TreeSort';
 import './TreeView.scss';
 import React = require('react');
-import { IconButton, Size } from 'browndash-components';
-import { TreeSort } from './TreeSort';
-import { SettingsManager } from '../../util/SettingsManager';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -165,7 +165,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.childDocList(this.fieldKey + '_annotations');
     }
     @computed get selected() {
-        return SelectionManager.IsSelected(this._docRef);
+        return this._docRef?.SELECTED;
     }
 
     childDocList(field: string) {
@@ -455,7 +455,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     embeddedPanelHeight = () => {
         const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
-            layoutDoc[Height](),
+            NumCast(layoutDoc._height),
             this.MAX_EMBED_HEIGHT,
             (() => {
                 const aspect = Doc.NativeAspect(layoutDoc);
@@ -464,7 +464,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
                         : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
-                    : (this.embeddedPanelWidth() * layoutDoc[Height]()) / layoutDoc[Width]();
+                    : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
             })()
         );
     };
@@ -741,7 +741,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             <div
                 className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
                 key="bullet"
-                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : 'view fields'}
+                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
                 onClick={this.bulletClick}
                 style={
                     this.props.treeView.outlineMode
@@ -872,7 +872,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
-                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
+                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return treeView.outlineMode ? null : (
                     <div
@@ -1265,7 +1265,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
                     const aspect = Doc.NativeAspect(childLayout);
-                    return aspect ? Math.min(childLayout[Width](), rowWidth()) / aspect : childLayout[Height]();
+                    return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height);
                 };
                 return (
                     <TreeView
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
new file mode 100644
index 000000000..00505dbe3
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -0,0 +1,75 @@
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { NumCast } from '../../../../fields/Types';
+import './CollectionFreeFormView.scss';
+import React = require('react');
+
+export interface CollectionFreeFormViewBackgroundGridProps {
+    panX: () => number;
+    panY: () => number;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    color: () => string;
+    isAnnotationOverlay?: boolean;
+    nativeDimScaling: () => number;
+    zoomScaling: () => number;
+    layoutDoc: Doc;
+    cachedCenteringShiftX: number;
+    cachedCenteringShiftY: number;
+}
+@observer
+export class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
+    chooseGridSpace = (gridSpace: number): number => {
+        if (!this.props.zoomScaling()) return gridSpace;
+        const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace;
+        return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2);
+    };
+    render() {
+        const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
+        const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
+        const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
+        const renderGridSpace = gridSpace * this.props.zoomScaling();
+        const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+        const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+        const strokeStyle = this.props.color();
+        return !this.props.nativeDimScaling() ? null : (
+            <canvas
+                className="collectionFreeFormView-grid"
+                width={w}
+                height={h}
+                style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
+                ref={el => {
+                    const ctx = el?.getContext('2d');
+                    if (ctx) {
+                        const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
+                        const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+                        ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
+                        ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
+                        ctx.clearRect(0, 0, w, h);
+                        if (ctx) {
+                            ctx.strokeStyle = strokeStyle;
+                            ctx.fillStyle = strokeStyle;
+                            ctx.beginPath();
+                            if (this.props.zoomScaling() > 1) {
+                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+                                    ctx.moveTo(x, Cy - h);
+                                    ctx.lineTo(x, Cy + h);
+                                }
+                                for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+                                    ctx.moveTo(Cx - w, y);
+                                    ctx.lineTo(Cx + w, y);
+                                }
+                            } else {
+                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace)
+                                    for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+                                        ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
+                                    }
+                            }
+                            ctx.stroke();
+                        }
+                    }
+                }}
+            />
+        );
+    }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index d93e44ab7..403fba67b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,5 +1,4 @@
 import { Doc, Field, FieldResult } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
@@ -29,6 +28,8 @@ export interface ViewDefBounds {
 }
 
 export interface PoolData {
+    pair: { layout: Doc; data?: Doc };
+    replica: string;
     x: number;
     y: number;
     z?: number;
@@ -41,9 +42,7 @@ export interface PoolData {
     opacity?: number;
     transition?: string;
     highlight?: boolean;
-    replica: string;
-    pointerEvents?: string; // without this, toggling lockPosition of a group/collection in a freeform view won't update until something else invalidates the freeform view's documents forcing -- this is a problem with doLayoutComputation which makes a performance test to insure somethingChanged
-    pair: { layout: Doc; data?: Doc };
+    pointerEvents?: string;
 }
 
 export interface ViewDefResult {
@@ -91,8 +90,8 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
         docMap.set(layout[Id], {
             x: NumCast(layout.x),
             y: NumCast(layout.y),
-            width: layout[Width](),
-            height: layout[Height](),
+            width: NumCast(layout._width),
+            height: NumCast(layout._height),
             pair: { layout, data },
             transition: 'all .3s',
             replica: '',
@@ -106,8 +105,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
     childPairs.forEach(({ layout, data }, i) => {
-        const aspect = layout[Height]() / layout[Width]();
-        const docSize = Math.min(Math.min(400, layout[Width]()), Math.min(400, layout[Width]()) / aspect) * burstScale;
+        const aspect = NumCast(layout._height) / NumCast(layout._width);
+        const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale;
         const deg = (i / childPairs.length) * Math.PI * 2;
         docMap.set(layout[Id], {
             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
@@ -156,7 +155,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
                 x: 0,
                 y: 0,
                 zIndex: 0,
-                width: 0, // should make doc hidden in CollectionFreefromDocumentView
+                width: 0, // should make doc hidden in CollectionFreeFormDocumentView
                 height: 0,
                 pair,
                 replica: '',
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 24a758d8c..aca6df3c9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field } from '../../../../fields/Doc';
-import { DocCss } from '../../../../fields/DocSymbols';
+import { Brushed, DocCss } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { Cast, NumCast, StrCast } from '../../../../fields/Types';
@@ -223,8 +223,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
         const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
         const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
-        const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
-        const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
+        const aActive = A.isSelected() || A.rootDoc[Brushed];
+        const bActive = B.isSelected() || B.rootDoc[Brushed];
 
         const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
         const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
@@ -239,11 +239,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             textX,
             textY,
             // fully connected
-           // pt1,
-           // pt2,
+            // pt1,
+            // pt2,
             // this code adds space between links
-             pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt1,
-             pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3*NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3*NumCast(link.link_displayArrow_scale, 3)] : pt2,
+            pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt1,
+            pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt2,
         };
     }
 
@@ -269,12 +269,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         //thickness varies linearly from 3px to 12px for increasing link count
         const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
 
-        const arrowScale = NumCast(link.link_displayArrow_scale, 3)
+        const arrowScale = NumCast(link.link_displayArrow_scale, 3);
         return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
             <>
                 <defs>
-                    <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4*arrowScale}`} markerHeight={`${3*arrowScale}`} refX="0" refY={`${1.5*arrowScale}`} orient="auto">
-                        <polygon points={`0 0, ${3*arrowScale} ${1.5*arrowScale}, 0 ${3*arrowScale}`} fill={stroke} />
+                    <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4 * arrowScale}`} markerHeight={`${3 * arrowScale}`} refX="0" refY={`${1.5 * arrowScale}`} orient="auto">
+                        <polygon points={`0 0, ${3 * arrowScale} ${1.5 * arrowScale}, 0 ${3 * arrowScale}`} fill={stroke} />
                     </marker>
                     <filter id="outline">
                         <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
new file mode 100644
index 000000000..856e195a3
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -0,0 +1,60 @@
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { PresBox } from '../../nodes/trails/PresBox';
+import './CollectionFreeFormView.scss';
+import React = require('react');
+import { CollectionFreeFormView } from './CollectionFreeFormView';
+
+export interface CollectionFreeFormPannableContentsProps {
+    rootDoc: Doc;
+    viewDefDivClick?: ScriptField;
+    children?: React.ReactNode | undefined;
+    transition?: string;
+    isAnnotationOverlay: boolean | undefined;
+    transform: () => string;
+    brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
+}
+
+@observer
+export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+    @computed get presPaths() {
+        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
+    }
+    // rectangle highlight used when following trail/link to a region of a collection that isn't a document
+    showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
+        !viewport ? null : (
+            <div
+                className="collectionFreeFormView-brushView"
+                style={{
+                    transform: `translate(${viewport.panX}px, ${viewport.panY}px)`,
+                    width: viewport.width,
+                    height: viewport.height,
+                    border: `orange solid ${viewport.width * 0.005}px`,
+                }}
+            />
+        );
+
+    render() {
+        return (
+            <div
+                className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+                onScroll={e => {
+                    const target = e.target as any;
+                    if (getComputedStyle(target)?.overflow === 'visible') {
+                        target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars
+                    }
+                }}
+                style={{
+                    transform: this.props.transform(),
+                    transition: this.props.transition,
+                    width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+                }}>
+                {this.props.children}
+                {this.presPaths}
+                {this.showViewport(this.props.brushedView())}
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index da0f7c893..e350c35cc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,20 +9,18 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
-import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
-import { InteractionUtils } from '../../../util/InteractionUtils';
 import { FollowLinkScript } from '../../../util/LinkFollower';
 import { ReplayMovements } from '../../../util/ReplayMovements';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -31,13 +29,13 @@ import { freeformScrollMode } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
-import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
 import { Timeline } from '../../animationtimeline/Timeline';
 import { ContextMenu } from '../../ContextMenu';
 import { GestureOverlay } from '../../GestureOverlay';
-import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
+import { CtrlKey } from '../../GlobalKeyHandler';
+import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
+import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -46,8 +44,9 @@ import { CreateImage } from '../../nodes/WebBoxRenderer';
 import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
-import { TabDocView } from '../TabDocView';
+import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
@@ -81,16 +80,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _downX: number = 0;
     private _downY: number = 0;
     private _downTime = 0;
-    private _inkToTextStartX: number | undefined;
-    private _inkToTextStartY: number | undefined;
-    private _wordPalette: Map<string, string> = new Map<string, string>();
     private _clusterDistance: number = 75;
     private _hitCluster: number = -1;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _renderCutoffData = observable.map<string, boolean>();
-    private _layoutPoolData = observable.map<string, PoolData>();
-    private _layoutSizeData = observable.map<string, { width?: number; height?: number }>();
-    private _cachedPool: Map<string, PoolData> = new Map();
     private _batch: UndoManager.Batch | undefined = undefined;
     private _brushtimer: any;
     private _brushtimer1: any;
@@ -110,9 +103,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private get autoResetFieldKey() {
         return (this.props.viewField ?? '') + '_freeform_autoReset';
     }
-    private get borderWidth() {
-        return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
-    }
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
     @observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan.  set to 0 to use document's transition time or default of 0
@@ -122,10 +112,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _deleteList: DocumentView[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
-    @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
     @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
+    @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
-    @computed get views() {
+    @computed get contentViews() {
         const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
         const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele);
         if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>);
@@ -170,19 +160,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.layout_fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
         return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
-    @computed get cachedGetLocalTransform(): Transform {
-        return Transform.Identity()
-            .scale(1 / this.zoomScaling())
-            .translate(this.panX(), this.panY());
-    }
-    @computed get cachedGetContainerTransform(): Transform {
-        return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+    @computed get panZoomXf() {
+        return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
     }
-    @computed get cachedGetTransform(): Transform {
-        return this.getContainerTransform()
-            .scale(this.props.isAnnotationOverlay ? 1 : 1 / this.nativeDim())
+    @computed get screenToLocalXf() {
+        return this.props
+            .ScreenToLocalTransform()
+            .scale(this.props.isAnnotationOverlay ? 1 : 1)
             .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
-            .transform(this.cachedGetLocalTransform);
+            .transform(this.panZoomXf);
     }
 
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
@@ -247,11 +233,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
     panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
-    contentTransform = () =>
+    PanZoomCenterXf = () =>
         this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
-    getTransform = () => this.cachedGetTransform.copy();
-    getLocalTransform = () => this.cachedGetLocalTransform.copy();
-    getContainerTransform = () => this.cachedGetContainerTransform.copy();
+    ScreenToLocalXf = () => this.screenToLocalXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     isAnyChildContentActive = () => this.props.isAnyChildContentActive();
     addLiveTextBox = (newBox: Doc) => {
@@ -260,7 +244,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
@@ -331,19 +315,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
-        return new Promise<Opt<DocumentView>>(res => {
+    getView = async (doc: Doc): Promise<Opt<DocumentView>> =>
+        new Promise<Opt<DocumentView>>(res => {
             if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
             const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
             findDoc(dv => res(dv));
         });
-    };
 
     @action
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
         if (!super.onInternalDrop(e, de)) return false;
         const refDoc = docDragData.droppedDocuments[0];
-        const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
+        const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
         const z = NumCast(refDoc.z);
         const x = (z ? xpo : xp) - docDragData.offset[0];
         const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -358,16 +341,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
+            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
                 const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000), false); // get non-default values for everything else
-                vals.x = x + NumCast(pvals.x) - dropPos[0];
-                vals.y = y + NumCast(pvals.y) - dropPos[1];
+                vals.x = NumCast(pvals.x) + delta.x;
+                vals.y = NumCast(pvals.y) + delta.y;
                 CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
             } else {
-                d.x = x + NumCast(d.x) - dropPos[0];
-                d.y = y + NumCast(d.y) - dropPos[1];
+                d.x = NumCast(d.x) + delta.x;
+                d.y = NumCast(d.y) + delta.y;
             }
             d._layout_modificationDate = new DateField();
             const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
@@ -451,15 +435,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+        const [xp, yp] = this.screenToLocalXf.transformPoint(de.x, de.y);
         if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
         else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
         else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
         return false;
     };
 
-    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY));
-
+    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToLocalXf.transformPoint(e.pageX, e.pageY));
+
+    static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
+        const doc2Layout = Doc.Layout(doc2);
+        const doc1Layout = Doc.Layout(doc1);
+        const x2 = NumCast(doc2.x) - clusterDistance;
+        const y2 = NumCast(doc2.y) - clusterDistance;
+        const w2 = NumCast(doc2Layout._width) + clusterDistance;
+        const h2 = NumCast(doc2Layout._height) + clusterDistance;
+        const x = NumCast(doc1.x) - clusterDistance;
+        const y = NumCast(doc1.y) - clusterDistance;
+        const w = NumCast(doc1Layout._width) + clusterDistance;
+        const h = NumCast(doc1Layout._height) + clusterDistance;
+        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    }
     pickCluster(probe: number[]) {
         return this.childLayoutPairs
             .map(pair => pair.layout)
@@ -477,16 +474,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             }, -1);
     }
 
-    tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) {
+    tryDragCluster(e: PointerEvent, cluster: number) {
         if (cluster !== -1) {
-            const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
+            const ptsParent = e;
             if (ptsParent) {
                 const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
                 const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this.props.moveDocument;
-                de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+                de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 DragManager.StartDocumentDrag(
                     clusterDocs.map(v => v.ContentDiv!),
                     de,
@@ -519,7 +516,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             docs.map(doc =>
                 this._clusterSets.map((set, i) =>
                     set.map(member => {
-                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                        if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                             docFirst.layout_cluster = i;
                         }
                     })
@@ -552,7 +549,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @action
-    updateCluster(doc: Doc) {
+    updateCluster = (doc: Doc) => {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
         if (this.props.Document._freeform_useClusters) {
             this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
@@ -560,7 +557,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.layout_cluster = -1;
             this._clusterSets.forEach((set, i) =>
                 set.forEach(member => {
-                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+                    if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) {
                         doc.layout_cluster = i;
                     }
                 })
@@ -581,9 +578,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._clusterSets[doc.layout_cluster ?? 0].push(doc);
             }
         }
-    }
+    };
 
-    getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
         let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         switch (property) {
             case StyleProp.BackgroundColor:
@@ -619,41 +616,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return false;
     };
 
-    @action
-    onPenUp = (e: PointerEvent): void => {
-        if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            document.removeEventListener('pointerup', this.onPenUp);
-            const currentCol = DocListCast(this.rootDoc.currentInkDoc);
-            const rootDocList = DocListCast(this.rootDoc.data);
-            currentCol.push(rootDocList[rootDocList.length - 1]);
-
-            this._batch?.end();
-        }
-    };
-
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = this._lastX = e.pageX;
         this._downY = this._lastY = e.pageY;
         this._downTime = Date.now();
-        if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
-            if (
-                !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
-                !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
-                !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
-            ) {
+        const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
+        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
+            if (!this.props.Document._isGroup) {
+                // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 // prettier-ignore
                 switch (Doc.ActiveTool) {
-                    case InkTool.Highlighter:  break;
+                    case InkTool.Highlighter: break;
                     case InkTool.Write: break;
-                    case InkTool.Pen:  break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+                    case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
                     case InkTool.Eraser:
                         this._batch = UndoManager.StartBatch('collectionErase');
                         setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
                         break;
                     case InkTool.None:
                         if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
-                            this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+                            this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
                         }
                         break;
@@ -662,29 +645,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    @action
-    handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
-        // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
-        const pt = me.changedTouches[0];
-        if (pt) {
-            this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
-            if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
-                this.removeMoveListeners();
-                this.addMoveListeners();
-                this.removeEndListeners();
-                this.addEndListeners();
-                if (Doc.ActiveTool === InkTool.None) {
-                    this._lastX = pt.pageX;
-                    this._lastY = pt.pageY;
-                    e.preventDefault();
-                    e.stopPropagation();
-                } else {
-                    e.preventDefault();
-                }
-            }
-        }
-    };
-
     public unprocessedDocs: Doc[] = [];
     public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>();
     @undoBatch
@@ -697,25 +657,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Triangle:
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
-                const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                console.log(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+                const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+                const inkWidth = ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale;
                 const inkDoc = Docs.Create.InkDocument(
-                    ActiveInkColor(),
-                    ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
-                    ActiveInkBezierApprox(),
-                    ActiveFillColor(),
-                    ActiveArrowStart(),
-                    ActiveArrowEnd(),
-                    ActiveDash(),
                     points,
-                    ActiveIsInkMask(),
-                    {
-                        title: ge.gesture.toString(),
-                        x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
-                        y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
-                        _width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
-                        _height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
-                    }
+                    { title: ge.gesture.toString(),
+                      x: B.x - inkWidth / 2,
+                      y: B.y - inkWidth / 2,
+                      _width: B.width + inkWidth,
+                      _height: B.height + inkWidth }, // prettier-ignore
+                    inkWidth
                 );
                 if (Doc.ActiveTool === InkTool.Write) {
                     this.unprocessedDocs.push(inkDoc);
@@ -725,69 +676,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 e.stopPropagation();
                 break;
             case GestureUtils.Gestures.Rectangle:
-                if (this._inkToTextStartX && this._inkToTextStartY) {
-                    const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
-                    const setDocs = this.getActiveDocuments().filter(s => DocCast(s.proto)?.type === DocumentType.RTF && s.color);
-                    const sets = setDocs.map(sd => Cast(sd.text, RichTextField)?.Text as string);
-                    if (sets.length && sets[0]) {
-                        this._wordPalette.clear();
-                        const colors = setDocs.map(sd => FieldValue(sd.color) as string);
-                        sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i])));
-                    }
-                    const inks = this.getActiveDocuments().filter(doc => {
-                        if (doc.type === 'ink') {
-                            const l = NumCast(doc.x);
-                            const r = l + doc[Width]();
-                            const t = NumCast(doc.y);
-                            const b = t + doc[Height]();
-                            const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
-                            return pass;
-                        }
-                        return false;
-                    });
-                    // const inkFields = inks.map(i => Cast(i.data, InkField));
-                    const strokes: InkData[] = [];
-                    inks.forEach(i => {
-                        const d = Cast(i.data, InkField);
-                        const x = NumCast(i.x);
-                        const y = NumCast(i.y);
-                        const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
-                        const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
-                        if (d) {
-                            strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
-                        }
+                const strokes = this.getActiveDocuments()
+                    .filter(doc => doc.type === DocumentType.INK)
+                    .map(i => {
+                        const d = Cast(i.stroke, InkField);
+                        const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+                        const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
+                        return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
                     });
 
-                    CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {
-                        const wordResults = results.filter((r: any) => r.category === 'inkWord');
-                        for (const word of wordResults) {
-                            const indices: number[] = word.strokeIds;
-                            indices.forEach(i => {
-                                const otherInks: Doc[] = [];
-                                indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
-                                inks[i].relatedInks = new List<Doc>(otherInks);
-                                const uniqueColors: string[] = [];
-                                Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
-                                inks[i].alternativeColors = new List<string>(uniqueColors);
-                                if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
-                                    inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
-                                } else if (word.alternates) {
-                                    for (const alt of word.alternates) {
-                                        if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
-                                            inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
-                                            break;
-                                        }
-                                    }
-                                }
-                            });
-                        }
-                    });
-                    this._inkToTextStartX = end[0];
-                }
+                CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
                 break;
             case GestureUtils.Gestures.Text:
                 if (ge.text) {
-                    const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+                    const B = this.screenToLocalXf.transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
                     e.stopPropagation();
                 }
@@ -810,7 +712,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
                 // reset zoom of freeform view to 1-to-1 on a shift + double click
-                this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+                this.zoomSmoothlyAboutPt(this.screenToLocalXf.transformPoint(e.clientX, e.clientY), 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -824,11 +726,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
+    pan = (e: PointerEvent): void => {
+        const ctrlKey = e.ctrlKey && !e.shiftKey;
+        const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
         this.props.DocumentView?.().clearViewTransition();
-        const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
+        const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     };
@@ -874,20 +779,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @action
-    onPointerMove = (e: PointerEvent): boolean => {
-        if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
-        if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
-            Doc.ActiveTool = InkTool.None;
-        } else {
-            if (this.tryDragCluster(e, this._hitCluster)) {
-                e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
-                return true;
-            }
-            // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
-            if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.getLocalTransform().inverse().Scale) {
-                this.pan(e);
-                e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
-            }
+    onPointerMove = (e: PointerEvent) => {
+        if (this.tryDragCluster(e, this._hitCluster)) {
+            e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
+            return true;
+        }
+        // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
+        if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.zoomScaling()) {
+            this.pan(e);
+            e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
         }
         return false;
     };
@@ -1025,18 +925,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return tVals;
     };
 
-    cleanUpInteractions = () => {
-        this.removeMoveListeners();
-        this.removeEndListeners();
-    };
-
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
         if (this.Document._isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
-        const [x, y] = this.getTransform().transformPoint(pointX, pointY);
-        const invTransform = this.getLocalTransform().inverse();
+        const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
+        const invTransform = this.panZoomXf.inverse();
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
@@ -1067,11 +962,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         e.stopPropagation();
         const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
         const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
-        switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
+        switch (
+            !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
+                Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
+                    e.ctrlKey && !CtrlKey? // otherwise, if ctrl key (pinch gesture) try to zoom else pan
+                        freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
+        ) {
             case freeformScrollMode.Pan:
-                // if ctrl is selected then zoom
-                if (!e.ctrlKey && this.props.isContentActive(true)) {
-                    this.scrollPan({ deltaX: -e.deltaX * this.getTransform().Scale, deltaY: e.shiftKey ? 0 : -e.deltaY * this.getTransform().Scale });
+                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) {
+                    const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
+                    const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
+                    this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
                     break;
                 }
             default:
@@ -1093,7 +994,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
             const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
             const measuredDocs = docs
-                .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') }))
+                .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } }))
                 .filter(({ pos, size }) => pos && size)
                 .map(({ pos, size }) => ({ pos: pos!, size: size! }));
             if (measuredDocs.length) {
@@ -1110,11 +1011,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
                     }
                 );
-
-                const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
-                const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
-                const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
-                const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+                const scaling = this.zoomScaling() * (this.props.NativeDimScaling?.() || 1);
+                const panelWidMax = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelWidMin = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 0 : 1);
+                const panelHgtMax = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelHgtMin = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 0 : 1);
                 if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
                 else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
                 if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
@@ -1124,7 +1025,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
             this.setPanZoomTransition(panTime);
             const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
-            const scale = 1 - minScale / this.getLocalTransform().inverse().Scale;
+            const scale = 1 - minScale / this.zoomScaling();
             const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
             const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
@@ -1205,11 +1106,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
         if (this.Document._isGroup) return;
         this.setPanZoomTransition(transitionTime);
-        const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+        const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
-        const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+        const newScreenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
-        const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
+        const newpan = this.screenToLocalXf.transformDirection(scrDelta.x, scrDelta.y);
         this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
         this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
     }
@@ -1217,7 +1118,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
         const layoutdoc = Doc.Layout(doc);
         const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
-        const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[Width](), NumCast(doc.y) + layoutdoc[Height]());
+        const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height));
         const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
 
         if (scale !== undefined) {
@@ -1284,11 +1185,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             ? 'none'
             : this.props.childPointerEvents?.() ??
               (this.props.viewDefDivClick || //
-              (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+              (engine === computePassLayout.name && !this.props.isSelected()) ||
               this.isContentActive() === false
                   ? 'none'
                   : this.props.pointerEvents?.());
-        console.log(`${this.rootDoc.title} pe = ` + pointerevents);
         return pointerevents;
     }
 
@@ -1299,16 +1199,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
         return (
-            <CollectionFreeFormDocumentView
+            <CollectionFreeFormDocumentViewWrapper
+                {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
                 DataDoc={childData}
                 Document={childLayout}
+                dragStarting={this.dragStarting}
+                dragEnding={this.dragEnding}
                 isGroupActive={this.props.isGroupActive}
                 renderDepth={this.props.renderDepth + 1}
-                replica={entry.replica}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
                 suppressSetHeight={this.layoutEngine ? true : false}
-                renderCutoffProvider={this.renderCutoffProvider}
+                RenderCutoffProvider={this.renderCutoffProvider}
                 CollectionFreeFormView={this}
                 LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
                 LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
@@ -1318,7 +1220,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onKey={this.onKeyDown}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 onBrowseClick={this.onBrowseClickHandler}
-                ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
+                ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
                 childFilters={this.childDocFilters}
@@ -1334,10 +1236,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 pinToPres={this.props.pinToPres}
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 docViewPath={this.props.docViewPath}
-                styleProvider={this.getClusterColor}
+                styleProvider={this.clusterStyleProvider}
                 dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
-                dataProvider={this.childDataProvider}
-                sizeProvider={this.childSizeProvider}
                 bringToFront={this.bringToFront}
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRegisterView}
@@ -1356,7 +1256,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 return (
                     (this.addDocument?.(
                         (doc instanceof Doc ? [doc] : doc).map(doc => {
-                            const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+                            const pt = this.screenToLocalXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
                             doc.x = pt[0];
                             doc.y = pt[1];
                             return doc;
@@ -1400,9 +1300,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
             y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
             z: Cast(z, 'number'),
-            rotation: rotation,
+            rotation,
             color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
-            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
+            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor),
             opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
@@ -1466,20 +1366,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }.bind(this)
     );
 
-    childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || ''));
-    childDataProvider = computedFn(
-        function childDataProvider(this: any, doc: Doc, replica: string) {
-            return this.childPositionProviderUnmemoized(doc, replica);
-        }.bind(this)
-    );
-
-    childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || ''));
-    childSizeProvider = computedFn(
-        function childSizeProvider(this: any, doc: Doc, replica: string) {
-            return this.childSizeProviderUnmemoized(doc, replica);
-        }.bind(this)
-    );
-
     doEngineLayout(
         poolData: Map<string, PoolData>,
         engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
@@ -1508,55 +1394,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
 
-    @observable _numLoaded = 1;
-    _lastPoolSize = 0;
     @action
     doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
-        const array = Array.from(newPool.entries());
-        this._lastPoolSize = array.length;
-        for (const entry of array) {
-            const lastPos = this._cachedPool.get(entry[0]); // last computed pos
-            const newPos = entry[1];
-            if (
-                !lastPos ||
-                newPos.color !== lastPos.color ||
-                newPos.backgroundColor !== lastPos.backgroundColor ||
-                newPos.opacity !== lastPos.opacity ||
-                newPos.x !== lastPos.x ||
-                newPos.y !== lastPos.y ||
-                newPos.z !== lastPos.z ||
-                newPos.rotation !== lastPos.rotation ||
-                newPos.zIndex !== lastPos.zIndex ||
-                newPos.transition !== lastPos.transition ||
-                newPos.pointerEvents !== lastPos.pointerEvents
-            ) {
-                this._layoutPoolData.set(entry[0], newPos);
-            }
-            if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
-                this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
-            }
-        }
-        // by returning undefined, we prevent an edit being made to layoutElements when nothing has happened
-        // this short circuit, prevents lots of downstream mobx invalidations which would have no effect but cause
-        // a distinct lag at the start of dragging.
-        // The reason we're here in the first place without a change is that when dragging a document,
-        // filters are changed on the annotation layers (eg. WebBox) which invalidate the childDoc list
-        // for the overlay views -- however, in many cases, this filter change doesn't actually affect anything
-        // (e.g, no annotations, or only opaque annotations).
-        this._cachedPool.clear();
-        Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
         const elements = computedElementData.slice();
         Array.from(newPool.entries())
             .filter(entry => this.isCurrent(entry[1].pair.layout))
-            .forEach((entry, i) => {
-                const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica);
-                const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica);
+            .forEach(entry =>
                 elements.push({
                     ele: this.getChildDocView(entry[1]),
-                    bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height },
+                    bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any,
                     inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
-                });
-            });
+                })
+            );
 
         this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
         return elements;
@@ -1586,14 +1435,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._disposers.groupBounds = reaction(
                     () => {
                         if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
-                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[Width](), height: cd[Height]() }));
+                            const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
                         }
                         return undefined;
                     },
                     cbounds => {
                         if (cbounds) {
-                            const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[Width]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[Height]() / 2];
+                            const c = [NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) / 2, NumCast(this.layoutDoc.y) + NumCast(this.layoutDoc._height) / 2];
                             const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
@@ -1621,7 +1470,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             ? 'none'
                             : this.props.childPointerEvents?.() ??
                                   (this.props.viewDefDivClick || //
-                                  (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+                                  (engine === computePassLayout.name && !this.props.isSelected()) ||
                                   this.isContentActive() === false
                                       ? 'none'
                                       : this.props.pointerEvents?.());
@@ -1630,18 +1479,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     { fireImmediately: true }
                 );
 
-                this._disposers.layoutComputation = reaction(
-                    () => this.doInternalLayoutComputation,
-                    ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)),
-                    { fireImmediately: true, name: 'layoutComputationReaction' }
-                );
-
                 this._disposers.active = reaction(
                     () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions
                     active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView()
                 );
             })
         );
+        this._disposers.layoutElements = reaction(
+            // layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters
+            () => this.doInternalLayoutComputation,
+            computation => (this._layoutElements = this.doLayoutComputation(computation.newPool, computation.computedElementData)),
+            { fireImmediately: true }
+        );
     }
 
     static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
@@ -1680,8 +1529,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
             this.props.docViewPath().lastElement().ContentDiv!,
-            this.layoutDoc[Width](),
-            this.layoutDoc[Height](),
+            NumCast(this.layoutDoc._width),
+            NumCast(this.layoutDoc._height),
             this.props.PanelWidth(),
             this.props.PanelHeight(),
             0,
@@ -1690,8 +1539,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             '',
             (iconFile, nativeWidth, nativeHeight) => {
                 this.dataDoc.icon = new ImageField(iconFile);
-                this.dataDoc['icon_nativeWidth'] = nativeWidth;
-                this.dataDoc['icon_nativeHeight'] = nativeHeight;
+                this.dataDoc.icon_nativeWidth = nativeWidth;
+                this.dataDoc.icon_nativeHeight = nativeHeight;
             }
         );
 
@@ -1726,6 +1575,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     componentWillUnmount() {
+        this.rootDoc[this.autoResetFieldKey] && this.resetView();
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
@@ -1738,7 +1588,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     promoteCollection = () => {
         const childDocs = this.childDocs.slice();
         childDocs.forEach(doc => {
-            const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+            const scr = this.screenToLocalXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
@@ -1802,20 +1652,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return;
         }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
-        appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
+        appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
         this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
-        this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
+        this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
 
         !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
+        !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
         !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
 
-        const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...');
-        const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : [];
-        !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
-        !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
-
         const options = ContextMenu.Instance.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         !this.props.isAnnotationOverlay &&
@@ -1834,16 +1680,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     @action
-    transcribeStrokes = (math: boolean) => {
+    transcribeStrokes = () => {
         if (this.props.Document._isGroup && this.props.Document.transcription) {
-            if (!math) {
-                const text = StrCast(this.props.Document.transcription);
-
-                const lines = text.split('\n');
-                const height = 30 + 15 * lines.length;
+            const text = StrCast(this.props.Document.transcription);
+            const lines = text.split('\n');
+            const height = 30 + 15 * lines.length;
 
-                this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
-            }
+            this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
         }
     };
 
@@ -1853,31 +1696,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         SnappingManager.clearSnapLines();
     };
     @action
-    dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, visited = new Set<Doc>()) => {
+    dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => {
         if (visited.has(this.rootDoc)) return;
         visited.add(this.rootDoc);
         showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup));
-        if (this.rootDoc._isGroup && this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
-            this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited);
-        }
         const activeDocs = this.getActiveDocuments();
-        const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+        const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
 
         const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
-        activeDocs.forEach(
-            doc =>
-                doc._isGroup &&
-                SnappingManager.GetIsResizing() !== doc &&
-                !DragManager.docsBeingDragged.includes(doc) &&
-                (DocumentManager.Instance.getDocumentView(doc)?.ComponentView as CollectionFreeFormView)?.dragStarting(snapToDraggedDoc, false, visited)
-        );
+        activeDocs
+            .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))
+            .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited));
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
-        const invXf = this.getTransform().inverse();
+        const invXf = this.screenToLocalXf.inverse();
         snappableDocs
             .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
@@ -1904,15 +1740,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
     });
 
-    get children() {
-        this.incrementalRender();
-        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
-        return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
+    // if a freeform view has any children, then the children will likely consist of a single child
+    // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
+    // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
+    @computed get underlayViews() {
+        return this.props.children ? [this.props.children] : [];
     }
 
     @computed get placeholder() {
         return (
-            <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
+            <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}>
                 <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span>
             </div>
         );
@@ -1921,7 +1758,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     brushedView = () => this._brushedView;
     gridColor = () =>
         DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
-            .fade(0.6)
+            .fade(0.5)
             .toString();
     @computed get backgroundGrid() {
         return (
@@ -1943,16 +1780,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         );
     }
     @computed get pannableContents() {
+        this.incrementalRender();
         return (
-            <CollectionFreeFormViewPannableContents
+            <CollectionFreeFormPannableContents
                 rootDoc={this.rootDoc}
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
-                transform={this.contentTransform}
+                transform={this.PanZoomCenterXf}
                 transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
                 viewDefDivClick={this.props.viewDefDivClick}>
-                {this.children}
-            </CollectionFreeFormViewPannableContents>
+                {this.underlayViews}
+                {this.contentViews}
+                <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+            </CollectionFreeFormPannableContents>
         );
     }
     @computed get marqueeView() {
@@ -1970,8 +1810,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 selectDocuments={this.selectDocuments}
                 addDocument={this.addDocument}
                 addLiveTextDocument={this.addLiveTextBox}
-                getContainerTransform={this.getContainerTransform}
-                getTransform={this.getTransform}
+                getContainerTransform={this.props.ScreenToLocalTransform}
+                getTransform={this.ScreenToLocalXf}
                 panXFieldKey={this.panXFieldKey}
                 panYFieldKey={this.panYFieldKey}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
@@ -1993,17 +1833,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     nativeDim = () => this.nativeDimScaling;
 
     @action
-    brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number) => {
+    brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number = 2500) => {
         this._brushtimer1 && clearTimeout(this._brushtimer1);
         this._brushtimer && clearTimeout(this._brushtimer);
         this._brushedView = undefined;
         this._brushtimer1 = setTimeout(
             action(() => {
                 this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 };
-                this._brushtimer = setTimeout(
-                    action(() => (this._brushedView = undefined)),
-                    2500
-                );
+                this._brushtimer = setTimeout(action(() => (this._brushedView = undefined)), holdTime); // prettier-ignore
             }),
             transTime + 1
         );
@@ -2080,139 +1917,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 }
 
-interface CollectionFreeFormOverlayViewProps {
-    elements: () => ViewDefResult[];
-}
-
-@observer
-class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps> {
-    render() {
-        return this.props
-            .elements()
-            .filter(ele => ele.bounds?.z)
-            .map(ele => ele.ele);
-    }
-}
-
-interface CollectionFreeFormViewPannableContentsProps {
-    rootDoc: Doc;
-    viewDefDivClick?: ScriptField;
-    children?: React.ReactNode | undefined;
-    transition?: string;
-    isAnnotationOverlay: boolean | undefined;
-    transform: () => string;
-    brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
-}
-
-@observer
-class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
-    @computed get presPaths() {
-        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
-    }
-    // rectangle highlight used when following trail/link to a region of a collection that isn't a document
-    showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
-        !viewport ? null : (
-            <div
-                className="collectionFreeFormView-brushView"
-                style={{
-                    transform: `translate(${viewport.panX}px, ${viewport.panY}px)`,
-                    width: viewport.width,
-                    height: viewport.height,
-                    border: `orange solid ${viewport.width * 0.005}px`,
-                }}
-            />
-        );
-
-    render() {
-        return (
-            <div
-                className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
-                onScroll={e => {
-                    const target = e.target as any;
-                    if (getComputedStyle(target)?.overflow === 'visible') {
-                        target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars
-                    }
-                }}
-                style={{
-                    transform: this.props.transform(),
-                    transition: this.props.transition,
-                    width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
-                }}>
-                {this.props.children}
-                {this.presPaths}
-                {this.showViewport(this.props.brushedView())}
-            </div>
-        );
-    }
-}
-
-interface CollectionFreeFormViewBackgroundGridProps {
-    panX: () => number;
-    panY: () => number;
-    PanelWidth: () => number;
-    PanelHeight: () => number;
-    color: () => string;
-    isAnnotationOverlay?: boolean;
-    nativeDimScaling: () => number;
-    zoomScaling: () => number;
-    layoutDoc: Doc;
-    cachedCenteringShiftX: number;
-    cachedCenteringShiftY: number;
-}
 @observer
-class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
-    chooseGridSpace = (gridSpace: number): number => {
-        if (!this.props.zoomScaling()) return gridSpace;
-        const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace;
-        return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2);
-    };
+class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
     render() {
-        const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
-        const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
-        const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
-        const renderGridSpace = gridSpace * this.props.zoomScaling();
-        const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
-        const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
-        const strokeStyle = this.props.color();
-        return !this.props.nativeDimScaling() ? null : (
-            <canvas
-                className="collectionFreeFormView-grid"
-                width={w}
-                height={h}
-                style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
-                ref={el => {
-                    const ctx = el?.getContext('2d');
-                    if (ctx) {
-                        const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
-                        const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
-                        ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
-                        ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
-                        ctx.clearRect(0, 0, w, h);
-                        if (ctx) {
-                            ctx.strokeStyle = strokeStyle;
-                            ctx.fillStyle = strokeStyle;
-                            ctx.beginPath();
-                            if (this.props.zoomScaling() > 1) {
-                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
-                                    ctx.moveTo(x, Cy - h);
-                                    ctx.lineTo(x, Cy + h);
-                                }
-                                for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
-                                    ctx.moveTo(Cx - w, y);
-                                    ctx.lineTo(Cx + w, y);
-                                }
-                            } else {
-                                for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace)
-                                    for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
-                                        ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
-                                    }
-                            }
-                            ctx.stroke();
-                        }
-                    }
-                }}
-            />
-        );
+        return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore
     }
 }
 
@@ -2224,13 +1932,13 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
             if (!focused) {
                 const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
                 let containers = dv.props.docViewPath();
-                let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                let parFfview = dv.CollectionFreeFormView;
                 for (var cont of containers) {
-                    parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                    parFfview = parFfview ?? cont.CollectionFreeFormView;
                 }
-                while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-                ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
+                ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
                 Doc.linkFollowHighlight(dv?.props.Document, false);
             }
         });
@@ -2260,8 +1968,8 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     );
 });
 ScriptingGlobals.add(function bringToFront() {
-    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc));
 });
 ScriptingGlobals.add(function sendToBack(doc: Doc) {
-    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true));
 });
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index edcc17afd..2092ecb7a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -22,7 +22,6 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { PreviewCursor } from '../../PreviewCursor';
 import { SubCollectionViewProps } from '../CollectionSubView';
-import { TabDocView } from '../TabDocView';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
 import React = require('react');
@@ -78,15 +77,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const bounds: MarqueeViewBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
         return bounds;
     }
-    get inkDoc() {
-        return this.props.Document;
-    }
-    get ink() {
-        return Cast(this.props.Document.ink, InkField);
-    }
-    set ink(value: Opt<InkField>) {
-        this.props.Document.ink = value;
-    }
 
     componentDidMount() {
         this.props.setPreviewCursor?.(this.setPreviewCursor);
@@ -218,22 +208,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        // if (this.props.pointerEvents?.() === 'none') return;
         this._downX = this._lastX = e.clientX;
         this._downY = this._lastY = e.clientY;
-        if (!(e.nativeEvent as any).marqueeHit) {
-            (e.nativeEvent as any).marqueeHit = true;
-            // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode
-            if (e.button === 2 || (e.button === 0 && (e.altKey || (this.props.isContentActive?.(true) && Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan)))) {
-                // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
-                this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
-                // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
-                e.preventDefault();
-                // }
-                // bcz: do we need this?   it kills the context menu on the main collection if !altKey
-                // e.stopPropagation();
-            } else PreviewCursor.Visible = false;
-        }
+
+        const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
+        // allow marquee if right drag/meta drag, or pan mode
+        if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) {
+            this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
+            e.preventDefault();
+        } else PreviewCursor.Visible = false;
     };
 
     @action
@@ -242,7 +225,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         this._lastY = e.pageY;
         this._lassoPts.push([e.clientX, e.clientY]);
         if (!e.cancelBubble) {
-            if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
+            if (!Utils.isClick(this._lastX, this._lastY, this._downX, this._downY, Date.now())) {
                 if (!this._commandExecuted) {
                     this.showMarquee();
                 }
@@ -262,8 +245,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             if (!e.shiftKey) {
                 SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
             }
-            // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
-            // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
             const docs = mselect.length ? mselect : [this.props.Document];
             this.props.selectDocuments(docs);
         }
@@ -333,12 +314,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (this.props.pointerEvents?.() === 'none') return;
         if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             if (Doc.ActiveTool === InkTool.None) {
-                if (!(e.nativeEvent as any).marqueeHit) {
-                    (e.nativeEvent as any).marqueeHit = true;
-                    if (!this.props.trySelectCluster(e.shiftKey)) {
-                        !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
-                    } else e.stopPropagation();
-                }
+                if (!this.props.trySelectCluster(e.shiftKey)) {
+                    !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+                } else e.stopPropagation();
             }
             // let the DocumentView stopPropagation of this event when it selects this document
         } else {
@@ -409,7 +387,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     @undoBatch
     @action
     pinWithView = () => {
-        TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds });
+        this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds });
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     };
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index cd8b7a0cc..274012000 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -359,7 +359,7 @@ export class CollectionGridView extends CollectionSubView() {
                 },
                 false
             );
-            if (this.props.isSelected(true)) e.stopPropagation();
+            if (this.props.isSelected()) e.stopPropagation();
         }
     };
 
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 9a2c79a22..4267a9059 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView() {
 
     componentDidMount() {
         this._widthDisposer = reaction(
-            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (doc[Width]() || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.rootDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
             width => this.childDocs.length && (this.layoutDoc._width = width),
             { fireImmediately: true }
         );
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 81453c0b8..c2586fb4b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -295,7 +295,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             const aspect = Doc.NativeAspect(layout, undefined, true);
             const width = () => this.lookupPixels(layout);
             const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
-            const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(height() * aspect, width()));
+            const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(height() * aspect, width()));
             const docheight = () => Math.min(docwidth() / aspect, height());
             const dxf = () =>
                 this.lookupIndividualTransform(layout)
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 04cfc5456..249c3551b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -292,7 +292,7 @@ export class CollectionMultirowView extends CollectionSubView() {
             const height = () => this.lookupPixels(layout);
             const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0);
             const docheight = () => Math.min(width() / aspect, height());
-            const docwidth = () => (layout._layout_forceReflow ? width() : Math.min(width(), docheight() * aspect));
+            const docwidth = () => (layout._layout_reflowHorizontal ? width() : Math.min(width(), docheight() * aspect));
             const dxf = () =>
                 this.lookupIndividualTransform(layout)
                     .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin) - (height() - docheight()) / 2)
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index f73c037f4..190e4ff2a 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,9 +1,8 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, observe, trace } from 'mobx';
+import { action, computed, observable, ObservableMap, observe } from 'mobx';
 import { observer } from 'mobx-react';
-import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Field, NumListCast, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
@@ -17,9 +16,9 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
+import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
@@ -86,7 +85,17 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get _selectedDocs() {
-        return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+        const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+        if (!selected.length) {
+            for (const sel of SelectionManager.Docs()) {
+                const contextPath = DocumentManager.GetContextPath(sel, true);
+                if (contextPath.includes(this.rootDoc)) {
+                    const parentInd = contextPath.indexOf(this.rootDoc);
+                    return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
+                }
+            }
+        }
+        return selected;
     }
 
     @computed get documentKeys() {
@@ -236,10 +245,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this.layoutDoc.sortDesc = desc;
     };
 
-    addRow = (doc: Doc | Doc[]) => {
-        const result: boolean = this.addDocument(doc);
-        return result;
-    };
+    addRow = (doc: Doc | Doc[]) => this.addDocument(doc);
 
     @undoBatch
     @action
@@ -550,9 +556,8 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     setColumnValues = (key: string, value: string) => {
-        let success: boolean = true;
-        this.childDocs.forEach(doc => success && KeyValueBox.SetField(doc, key, value));
-        return success;
+        this.childDocs.forEach(doc => KeyValueBox.SetField(doc, key, value));
+        return true;
     };
 
     @action
@@ -577,9 +582,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    closeFilterMenu = () => {
-        this._filterColumnIndex = undefined;
-    };
+    closeFilterMenu = () => (this._filterColumnIndex = undefined);
 
     openContextMenu = (x: number, y: number, index: number) => {
         this.closeColumnMenu();
@@ -836,7 +839,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                 <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }}></div>
                 <div
                     className="schema-table"
-                    style={{ width: `calc(100% - ${this.previewWidth + 4}px)` }}
+                    style={{ width: `calc(100% - ${this.previewWidth}px)` }}
                     onWheel={e => this.props.isContentActive() && e.stopPropagation()}
                     ref={r => {
                         // prevent wheel events from passively propagating up through containers
@@ -932,51 +935,68 @@ interface CollectionSchemaViewDocsProps {
 
 @observer
 class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
-    tableWidthFunc = () => this.props.schema.tableWidth;
-    childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - index * this.props.rowHeight()));
     render() {
         return (
             <div className="schema-table-content" ref={this.props.setRef} style={{ height: `calc(100% - ${CollectionSchemaView._newNodeInputHeight + this.props.rowHeight()}px)` }}>
-                {this.props.childDocs().docs.map((doc: Doc, index: number) => {
-                    const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.schema.props.DataDoc;
-                    return (
-                        <div className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
-                            <DocumentView
-                                key={doc[Id]}
-                                {...this.props.schema.props}
-                                LayoutTemplate={this.props.schema.props.childLayoutTemplate}
-                                LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
-                                Document={doc}
-                                DataDoc={dataDoc}
-                                yPadding={index}
-                                renderDepth={this.props.schema.props.renderDepth + 1}
-                                PanelWidth={this.tableWidthFunc}
-                                PanelHeight={this.props.rowHeight}
-                                styleProvider={DefaultStyleProvider}
-                                waitForDoubleClickToClick={returnNever}
-                                defaultDoubleClick={returnIgnore}
-                                dragAction="move"
-                                onClickScriptDisable="always"
-                                focus={this.props.schema.focusDocument}
-                                childFilters={this.props.schema.childDocFilters}
-                                childFiltersByRanges={this.props.schema.childDocRangeFilters}
-                                searchFilterDocs={this.props.schema.searchFilterDocs}
-                                rootSelected={this.props.schema.rootSelected}
-                                ScreenToLocalTransform={this.childScreenToLocal(index)}
-                                bringToFront={emptyFunction}
-                                isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
-                                isContentActive={emptyFunction}
-                                whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
-                                hideDecorations={true}
-                                hideTitle={true}
-                                hideDocumentButtonBar={true}
-                                hideLinkAnchors={true}
-                                layout_fitWidth={returnTrue}
-                            />
-                        </div>
-                    );
-                })}
+                {this.props.childDocs().docs.map((doc: Doc, index: number) => (
+                    <div className="schema-row-wrapper" style={{ height: this.props.rowHeight() }}>
+                        <CollectionSchemaViewDoc doc={doc} schema={this.props.schema} index={index} rowHeight={this.props.rowHeight} />
+                    </div>
+                ))}
             </div>
         );
     }
 }
+
+interface CollectionSchemaViewDocProps {
+    schema: CollectionSchemaView;
+    index: number;
+    doc: Doc;
+    rowHeight: () => number;
+}
+
+@observer
+class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocProps> {
+    tableWidthFunc = () => this.props.schema.tableWidth;
+    screenToLocalXf = () => this.props.schema.props.ScreenToLocalTransform().translate(0, -this.props.rowHeight() - this.props.index * this.props.rowHeight());
+    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+        if (property === StyleProp.Opacity) return 1;
+        return DefaultStyleProvider(doc, props, property);
+    };
+    render() {
+        return (
+            <DocumentView
+                key={this.props.doc[Id]}
+                {...this.props.schema.props}
+                LayoutTemplate={this.props.schema.props.childLayoutTemplate}
+                LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey, this.props.index)}
+                Document={this.props.doc}
+                DataDoc={undefined}
+                renderDepth={this.props.schema.props.renderDepth + 1}
+                PanelWidth={this.tableWidthFunc}
+                PanelHeight={this.props.rowHeight}
+                styleProvider={this.noOpacityStyleProvider}
+                waitForDoubleClickToClick={returnNever}
+                defaultDoubleClick={returnIgnore}
+                dragAction="move"
+                onClickScriptDisable="always"
+                focus={this.props.schema.focusDocument}
+                childFilters={this.props.schema.childDocFilters}
+                childFiltersByRanges={this.props.schema.childDocRangeFilters}
+                searchFilterDocs={this.props.schema.searchFilterDocs}
+                rootSelected={this.props.schema.rootSelected}
+                ScreenToLocalTransform={this.screenToLocalXf}
+                bringToFront={emptyFunction}
+                dragWhenActive={true}
+                isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
+                isContentActive={emptyFunction}
+                whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
+                hideDecorations={true}
+                hideTitle={true}
+                hideDocumentButtonBar={true}
+                hideLinkAnchors={true}
+                layout_fitWidth={returnTrue}
+            />
+        );
+    }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 4e418728f..7346c4f12 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -1,12 +1,16 @@
 import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton, Size } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
+import { CgClose } from 'react-icons/cg';
+import { FaExternalLinkAlt } from 'react-icons/fa';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
+import { Transform } from '../../../util/Transform';
 import { undoable } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -15,16 +19,14 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { CollectionSchemaView } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 import { SchemaTableCell } from './SchemaTableCell';
-import { Transform } from '../../../util/Transform';
-import { IconButton, Size } from 'browndash-components';
-import { CgClose } from 'react-icons/cg';
-import { FaExternalLinkAlt } from 'react-icons/fa';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
 
+interface SchemaRowBoxProps {
+    rowIndex: number;
+}
 @observer
-export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(SchemaRowBox, fieldKey);
+export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRowBoxProps>() {
+    public static LayoutString(fieldKey: string, rowIndex: number) {
+        return FieldView.LayoutString(SchemaRowBox, fieldKey).replace('fieldKey', `rowIndex={${rowIndex}} fieldKey`);
     }
 
     private _ref: HTMLDivElement | null = null;
@@ -170,7 +172,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             transform={() => {
                                 const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index;
                                 const x = this.schemaView?.displayColumnWidths.reduce((p, c, i) => (i <= ind ? p + c : p), 0);
-                                const y = (this.props.yPadding ?? 0) * this.props.PanelHeight();
+                                const y = (this.props.rowIndex ?? 0) * this.props.PanelHeight();
                                 return new Transform(x + CollectionSchemaView._rowMenuWidth, y, 1);
                             }}
                         />
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index c129d29eb..44e8efe23 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -63,7 +63,6 @@ $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
 $docDecorations-zindex: 998; // then doc decorations appear over everything else
 $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
-$COLLECTION_BORDER_WIDTH: 0;
 $SCHEMA_DIVIDER_WIDTH: 4;
 $MINIMIZED_ICON_SIZE: 24;
 $MAX_ROW_HEIGHT: 44px;
@@ -80,7 +79,6 @@ $DATA_VIZ_TABLE_ROW_HEIGHT: 30;
 :export {
     contextMenuZindex: $contextMenu-zindex;
     SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
-    COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
     MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
     MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
     SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 3db498e77..bcbb1f068 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -1,7 +1,6 @@
 interface IGlobalScss {
     contextMenuZindex: string; // context menu shows up over everything
     SCHEMA_DIVIDER_WIDTH: string;
-    COLLECTION_BORDER_WIDTH: string;
     MINIMIZED_ICON_SIZE: string;
     MAX_ROW_HEIGHT: string;
     SEARCH_THUMBNAIL_SIZE: string;
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 894afebfd..3af523d73 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,25 +1,24 @@
 import { Colors } from 'browndash-components';
-import { runInAction, action } from 'mobx';
-import { aggregateBounds } from '../../../Utils';
-import { Doc } from '../../../fields/Doc';
-import { Width, Height } from '../../../fields/DocSymbols';
+import { action, runInAction } from 'mobx';
+import { Doc, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
-import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { aggregateBounds } from '../../../Utils';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoable, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { GestureOverlay } from '../GestureOverlay';
+import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke';
 import { InkTranscription } from '../InkTranscription';
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor, InkingStroke } from '../InkingStroke';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { WebBox } from '../nodes/WebBox';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentView } from '../nodes/DocumentView';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { WebBox } from '../nodes/WebBox';
 
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views().length <= 0;
@@ -53,7 +52,9 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
-                CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
+                const obj: { [key: string]: Opt<string> } = {};
+                obj[fieldKey] = color;
+                CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, obj);
             } else {
                 dv.rootDoc['_' + fieldKey] = color;
             }
@@ -92,7 +93,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
         if (NumCast(selected?.Document.z) >= 1) return true;
         return false;
     }
-    selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
+    selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
 ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) {
@@ -180,16 +181,22 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     map.get(attr)?.setDoc?.();
 });
 
-type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
+type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
 type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
 
 ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
     const textView = RichTextMenu.Instance?.TextView;
     const editorView = textView?.EditorView;
     // prettier-ignore
-    const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) =>
-        [ where, {      checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false),
-                        toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]);
+    const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
+        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  SelectionManager.Docs().some(doc => doc.layout_centered):
+                                                                              (RichTextMenu.Instance.textAlign === where)): 
+                                                         where === 'vcent' ? BoolCast(Doc.UserDoc().layout_centered):
+                                                                              (Doc.UserDoc().textAlign ===where) ? true:false),
+                        toggle: () => (editorView?.state ? (where === 'vcent' ? SelectionManager.Docs().forEach(doc => doc.layout_centered = !doc.layout_centered):
+                                                                              RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
+                                                            where === 'vcent' ?  Doc.UserDoc().layout_centered = !Doc.UserDoc().layout_centered:
+                                                                              (Doc.UserDoc().textAlign = where))}]); // prettier-ignore
     // prettier-ignore
     const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
         [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
@@ -239,8 +246,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
                 action(d => {
                     const x = NumCast(d.x);
                     const y = NumCast(d.y);
-                    const width = d[Width]();
-                    const height = d[Height]();
+                    const width = NumCast(d._width);
+                    const height = NumCast(d._height);
                     bounds.push({ x, y, width, height });
                 })
             );
@@ -277,8 +284,8 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
             // TODO: nda - this is the code to actually get a new grouped collection
             const newCollection = marqViewRef?.getCollection(selected, undefined, true);
             if (newCollection) {
-                newCollection.height = newCollection[Height]();
-                newCollection.width = newCollection[Width]();
+                newCollection.height = NumCast(newCollection._height);
+                newCollection.width = NumCast(newCollection._width);
             }
 
             // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index f99011b8f..7f0a39550 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -1,9 +1,9 @@
 .collectionFreeFormDocumentView-container {
-    transform-origin: left top;
+    transform-origin: 50% 50%;
     position: absolute;
     background-color: transparent;
     touch-action: manipulation;
     top: 0;
     left: 0;
-    //pointer-events: none;
+    pointer-events: none;
 }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 085f9f023..421d431b3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
@@ -6,10 +6,9 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { numberRange } from '../../../Utils';
+import { numberRange, OmitKeys } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SelectionManager } from '../../util/SelectionManager';
-import { Transform } from '../../util/Transform';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
@@ -17,26 +16,113 @@ import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import React = require('react');
 
-export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
-    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
-    sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
-    renderCutoffProvider: (doc: Doc) => boolean;
+export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
+    x: number;
+    y: number;
+    z: number;
+    width: number;
+    height: number;
     zIndex?: number;
+    rotation?: number;
+    color?: string;
+    backgroundColor?: string;
+    opacity?: number;
+    highlight?: boolean;
+    transition?: string;
     dataTransition?: string;
-    replica: string;
+    RenderCutoffProvider: (doc: Doc) => boolean;
+    CollectionFreeFormView: CollectionFreeFormView;
+}
+@observer
+export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps {
+    @observable X = this.props.x;
+    @observable Y = this.props.y;
+    @observable Z = this.props.z;
+    @observable ZIndex = this.props.zIndex;
+    @observable Rotation = this.props.rotation;
+    @observable Opacity = this.props.opacity;
+    @observable BackgroundColor = this.props.backgroundColor;
+    @observable Color = this.props.color;
+    @observable Highlight = this.props.highlight;
+    @observable Width = this.props.width;
+    @observable Height = this.props.height;
+    @observable Transition = this.props.transition;
+    @observable DataTransition = this.props.dataTransition;
+    CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
+    RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
+
+    @computed get WrapperKeys() {
+        return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
+                  .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
+    }
+
+    // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing.
+    // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
+    w_X = ()               => this.X; // prettier-ignore
+    w_Y = ()               => this.Y; // prettier-ignore
+    w_Z = ()               => this.Z; // prettier-ignore
+    w_ZIndex = ()          => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore
+    w_Rotation = ()        => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore
+    w_Opacity = ()         => this.Opacity; // prettier-ignore
+    w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore
+    w_Color = ()           => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore
+    w_Highlight = ()       => this.Highlight; // prettier-ignore
+    w_Width = ()           => this.Width; // prettier-ignore
+    w_Height = ()          => this.Height; // prettier-ignore
+    w_Transition = ()      => this.Transition; // prettier-ignore
+    w_DataTransition = ()  => this.DataTransition; // prettier-ignore
+
+    PanelWidth = ()        => this.Width || this.props.PanelWidth?.(); // prettier-ignore
+    PanelHeight = ()       => this.Height || this.props.PanelHeight?.(); // prettier-ignore
+    @action
+    componentDidUpdate() {
+        this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower]));
+    }
+    render() {
+        const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
+        return (
+            <CollectionFreeFormDocumentView
+                {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
+                {...layoutProps}
+                PanelWidth={this.PanelWidth}
+                PanelHeight={this.PanelHeight}
+            />
+        );
+    }
+}
+export interface CollectionFreeFormDocumentViewProps {
+    w_X: () => number;
+    w_Y: () => number;
+    w_Z: () => number;
+    w_ZIndex?: () => number;
+    w_Rotation?: () => number;
+    w_Color: () => string;
+    w_BackgroundColor: () => string;
+    w_Opacity: () => number | undefined;
+    w_Highlight: () => boolean | undefined;
+    w_Transition: () => string | undefined;
+    w_Width: () => number;
+    w_Height: () => number;
+    w_DataTransition: () => string | undefined;
+    PanelWidth: () => number;
+    PanelHeight: () => number;
+    RenderCutoffProvider: (doc: Doc) => boolean;
     CollectionFreeFormView: CollectionFreeFormView;
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
+    get displayName() { // this makes mobx trace() statements more descriptive
+        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
+    } // prettier-ignore
     public static animFields: { key: string; val?: number }[] = [
-        { key: '_height' },
-        { key: '_width' },
         { key: 'x' },
         { key: 'y' },
+        { key: 'opacity', val: 1 },
+        { key: '_height' },
+        { key: '_width' },
         { key: '_rotation', val: 0 },
         { key: '_layout_scrollTop' },
-        { key: 'opacity', val: 1 },
         { key: '_currentFrame' },
         { key: 'freeform_scale', val: 1 },
         { key: 'freeform_panX' },
@@ -44,52 +130,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     ]; // fields that are configured to be animatable using animation frames
     public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
     public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
-    @observable _animPos: number[] | undefined = undefined;
-    @observable _contentView: DocumentView | undefined | null;
-    get displayName() {
-        // this makes mobx trace() statements more descriptive
-        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
-    }
 
-    get transform() {
-        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`;
-    }
-    get X() {
-        return this.dataProvider?.x ?? NumCast(this.Document.x);
-    }
-    get Y() {
-        return this.dataProvider?.y ?? NumCast(this.Document.y);
-    }
-    get ZInd() {
-        return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
-    }
-    get Rot() {
-        return this.dataProvider?.rotation ?? NumCast(this.Document._rotation);
-    }
-    get Opacity() {
-        return this.dataProvider?.opacity;
-    }
-    get BackgroundColor() {
-        return this.dataProvider?.backgroundColor ?? Cast(this.Document._backgroundColor, 'string', null);
-    }
-    get Color() {
-        return this.dataProvider?.color ?? Cast(this.Document._color, 'string', null);
-    }
-    @computed get dataProvider() {
-        return this.props.dataProvider?.(this.props.Document, this.props.replica);
-    }
-    @computed get sizeProvider() {
-        return this.props.sizeProvider?.(this.props.Document, this.props.replica);
+    get CollectionFreeFormView() {
+        return this.props.CollectionFreeFormView;
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
-            // prettier-ignore
             switch (property) {
-                case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
-                case StyleProp.BackgroundColor: return this.BackgroundColor;
-                case StyleProp.Color:           return this.Color;
-            }
+                case StyleProp.Opacity:         return this.props.w_Opacity(); // only change the opacity for this specific document, not its children
+                case StyleProp.BackgroundColor: return this.props.w_BackgroundColor();
+                case StyleProp.Color:           return this.props.w_Color();
+            } // prettier-ignore
         }
         return this.props.styleProvider?.(doc, props, property);
     };
@@ -126,21 +178,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         });
     }
 
-    public static setupZoom(doc: Doc, targDoc: Doc) {
-        const width = new List<number>();
-        const height = new List<number>();
-        const top = new List<number>();
-        const left = new List<number>();
-        width.push(NumCast(targDoc._width));
-        height.push(NumCast(targDoc._height));
-        top.push(NumCast(targDoc._height) / -2);
-        left.push(NumCast(targDoc._width) / -2);
-        doc['viewfinder-width-indexed'] = width;
-        doc['viewfinder-height-indexed'] = height;
-        doc['viewfinder-top-indexed'] = top;
-        doc['viewfinder-left-indexed'] = left;
-    }
-
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
@@ -182,16 +219,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         }
     };
 
-    dragEnding = () => this.props.CollectionFreeFormView?.dragEnding();
-    dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true);
-
     nudge = (x: number, y: number) => {
-        this.props.Document.x = NumCast(this.props.Document.x) + x;
-        this.props.Document.y = NumCast(this.props.Document.y) + y;
+        const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y);
+        this.props.Document.x = this.props.w_X() + locX;
+        this.props.Document.y = this.props.w_Y() + locY;
     };
-    panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.();
-    panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.();
-    screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
+    screenToLocalTransform = () =>
+        this.props
+            .ScreenToLocalTransform()
+            .translate(-this.props.w_X(), -this.props.w_Y())
+            .rotateDeg(-(this.props.w_Rotation?.() || 0));
     returnThis = () => this;
 
     /// this indicates whether the doc view is activated because of its relationshop to a group
@@ -200,38 +237,29 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     //  'inactive' - this is a group child but it is not active
     //  undefined - this is not activated by a group
     isGroupActive = () => {
-        if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
+        if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
         const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
         return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
     };
+    public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
     render() {
         TraceMobx();
-        const divProps: DocumentViewProps = {
-            ...this.props,
-            CollectionFreeFormDocumentView: this.returnThis,
-            styleProvider: this.styleProvider,
-            ScreenToLocalTransform: this.screenToLocalTransform,
-            PanelWidth: this.panelWidth,
-            PanelHeight: this.panelHeight,
-            isGroupActive: this.isGroupActive,
-        };
+        const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
         return (
             <div
-                className="collectionFreeFormDocumentView-container"
+                className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
                 style={{
-                    width: this.panelWidth(),
-                    height: this.panelHeight(),
-                    transform: this.transform,
-                    transformOrigin: '50% 50%',
-                    transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
-                    zIndex: this.ZInd,
-                    display: this.sizeProvider?.width ? undefined : 'none',
-                    pointerEvents: 'none',
+                    width: this.props.PanelWidth(),
+                    height: this.props.PanelHeight(),
+                    transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`,
+                    transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()),
+                    zIndex: this.props.w_ZIndex?.(),
+                    display: this.props.w_Width?.() ? undefined : 'none',
                 }}>
-                {this.props.renderCutoffProvider(this.props.Document) ? (
-                    <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} />
+                {this.props.RenderCutoffProvider(this.props.Document) ? (
+                    <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
-                    <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} />
+                    <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
                 )}
             </div>
         );
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 1b6fe5748..23ffd1080 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -3,10 +3,12 @@ import { action } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Height } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { StrCast } from '../../../fields/Types';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { DashColor } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
@@ -14,8 +16,6 @@ import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth }
 import './ColorBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
 import { RichTextMenu } from './formattedText/RichTextMenu';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { DashColor } from '../../../Utils';
 
 @observer
 export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
-        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]());
+        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.rootDoc._height), this.props.PanelWidth() / NumCast(this.rootDoc._width));
         return (
             <div
                 className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
@@ -84,9 +84,6 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 }
 
-
-ScriptingGlobals.add(
-    function interpColors(c1:string, c2:string, weight=0.5) {
-        return DashColor(c1).mix(DashColor(c2),weight)
-    }
-)
\ No newline at end of file
+ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
+    return DashColor(c1).mix(DashColor(c2), weight);
+});
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index b09fcd882..ff394e5f5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -20,7 +20,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ComparisonBox, fieldKey);
     }
-    protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined;
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
 
     @observable _animating = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index f3670622a..e712a25a0 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -41,6 +41,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _ffref = React.createRef<CollectionFreeFormView>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+    anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
+    crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
     @observable _marqueeing: number[] | undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
@@ -255,7 +257,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     // toggles for user to decide which chart type to view the data in
-    renderVizView = () => {
+    @computed get renderVizView() {
+        const scale = this.props.NativeDimScaling?.() || 1;
         const sharedProps = {
             rootDoc: this.rootDoc,
             layoutDoc: this.layoutDoc,
@@ -267,16 +270,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:
-                return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
-            case DataVizView.LINECHART:
-                return <LineChart vizBox={this} {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
-            case DataVizView.HISTOGRAM:
-                return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
-            case DataVizView.PIECHART:
-                return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
-        }
-    };
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+            case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
+            case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
+            case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
+            margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />;
+        } // prettier-ignore
+    }
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
@@ -319,6 +319,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     render() {
+        const scale = this.props.NativeDimScaling?.() || 1;
+
         return !this.records.length ? (
             // displays how to get data into the DataVizBox if its empty
             <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div>
@@ -328,15 +330,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 onPointerDown={this.marqueeDown}
                 style={{
                     pointerEvents: this.props.isContentActive() === true ? 'all' : 'none',
+                    width: `${100 / scale}%`,
+                    height: `${100 / scale}%`,
+                    transform: `scale(${scale})`,
+                    position: 'absolute',
                 }}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}
                 >
                 <div className={'datatype-button'}>
-                    <Toggle text={'TABLE'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz == DataVizView.TABLE} />
-                    <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.LINECHART} />
-                    <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} />
-                    <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} />
+                    <Toggle text={'  TABLE  '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
+                    <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
+                    <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
+                    <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
                 </div>
 
                 <CollectionFreeFormView
@@ -358,7 +364,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     addDocument={this.addDocument}>
                 </CollectionFreeFormView>
 
-                {this.renderVizView()}
+                {this.renderVizView}
                 <div className="dataviz-sidebar" 
                      style={{ width: `${this.sidebarWidthPercent}`, 
                      backgroundColor: `${this.sidebarColor}` }}
@@ -383,20 +389,36 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 {this.sidebarHandle}
                 {this.annotationLayer}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                    // <MarqueeAnnotator
+                    //     rootDoc={this.rootDoc}
+                    //     scrollTop={0}
+                    //     down={this._marqueeing}
+                    //     scaling={this.props.NativeDimScaling}
+                    //     docView={this.props.DocumentView}
+                    //     addDocument={this.sidebarAddDocument}
+                    //     finishMarquee={this.finishMarquee}
+                    //     savedAnnotations={this.savedAnnotations}
+                    //     selectionText={returnEmptyString}
+                    //     annotationLayer={this._annotationLayer.current}
+                    //     mainCont={this._mainCont.current}
+                    //     highlightDragSrcColor={''}
+                    // />
+
                     <MarqueeAnnotator
-                        rootDoc={this.rootDoc}
-                        scrollTop={0}
-                        down={this._marqueeing}
-                        scaling={this.props.NativeDimScaling}
-                        docView={this.props.docViewPath().slice(-1)[0]}
-                        addDocument={this.sidebarAddDocument}
-                        finishMarquee={this.finishMarquee}
-                        savedAnnotations={this.savedAnnotations}
-                        selectionText={returnEmptyString}
-                        annotationLayer={this._annotationLayer.current}
-                        mainCont={this._mainCont.current}
-                        highlightDragSrcColor={''}
-                    />
+                            rootDoc={this.rootDoc}
+                            getPageFromScroll={undefined}
+                            anchorMenuClick={this.anchorMenuClick}
+                            scrollTop={0}
+                            annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+                            addDocument={this.sidebarAddDocument}
+                            docView={this.props.DocumentView!}
+                            finishMarquee={this.finishMarquee}
+                            savedAnnotations={this.savedAnnotations}
+                            selectionText={returnEmptyString}
+                            annotationLayer={this._annotationLayer.current}
+                            mainCont={this._mainCont.current}
+                            anchorMenuCrop={this.crop}
+                        />
                     
                 )}
             </div>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index df65810b5..4622b470a 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -88,11 +88,7 @@ export class PieChart extends React.Component<PieChartProps> {
     componentDidMount = () => {
         this._disposers.chartData = reaction(
             () => ({ dataSet: this._pieChartData, w: this.width, h: this.height }),
-            ({ dataSet, w, h }) => {
-                if (dataSet!.length > 0) {
-                    this.drawChart(dataSet, w, h);
-                }
-            },
+            ({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h),
             { fireImmediately: true }
         );
     };
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 3685a198e..012021dda 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -220,7 +220,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter(rowId => this.startID <= rowId && rowId <= this.endID)
+                                    .filter((rowId, i) => this.startID <= i && i <= this.endID)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index e1de2fa76..8c8c987fe 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -98,7 +98,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
     };
     render() {
         const style: { [key: string]: any } = {};
-        const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
+        const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
         const replacer = (match: any, expr: string, offset: any, string: any) => {
             // bcz: this executes a script to convert a propery expression string:  { script }  into a value
             return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
@@ -119,7 +119,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 @observer
 export class DocumentContentsView extends React.Component<
     DocumentViewProps & {
-        isSelected: (outsideReaction: boolean) => boolean;
+        isSelected: () => boolean;
         select: (ctrl: boolean) => void;
         NativeDimScaling?: () => number;
         setHeight?: (height: number) => void;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 931594568..406a1b8fb 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -61,7 +61,6 @@
         .documentView-htmlOverlayInner {
             box-shadow: black 0.2vw 0.2vw 0.8vw;
             background: rgb(255, 255, 255);
-            overflow: auto;
             position: relative;
             margin: auto;
             padding: 20px;
@@ -120,6 +119,10 @@
         display: flex;
         justify-content: center;
         align-items: center;
+        position: relative; // allows contents to be positioned relative/below title
+        > .formattedTextBox {
+            position: absolute; // position a child text box
+        }
 
         .sharingIndicator {
             height: 30px;
@@ -183,12 +186,15 @@
             top: 0;
             width: 100%;
             height: 14;
-            background: rgba(0, 0, 0, 0.4);
+            opacity: 0.5;
             text-align: center;
             text-overflow: ellipsis;
             white-space: pre;
             position: absolute;
             display: flex; // this allows title field dropdown to be inline with editable title
+            &:hover {
+                opacity: 1;
+            }
         }
 
         .documentView-titleWrapper-hover {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 98b13f90f..0d6b88392 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -24,7 +24,6 @@ import { Networking } from '../../Network';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
-import { InteractionUtils } from '../../util/InteractionUtils';
 import { FollowLinkScript } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -64,6 +63,14 @@ declare class MediaRecorder {
     constructor(e: any);
 }
 
+export enum OpenWhereMod {
+    none = '',
+    left = 'left',
+    right = 'right',
+    top = 'top',
+    bottom = 'bottom',
+    keyvalue = 'keyValue',
+}
 export enum OpenWhere {
     lightbox = 'lightbox',
     add = 'add',
@@ -79,14 +86,7 @@ export enum OpenWhere {
     inParent = 'inParent',
     inParentFromScreen = 'inParentFromScreen',
     overlay = 'overlay',
-}
-export enum OpenWhereMod {
-    none = '',
-    left = 'left',
-    right = 'right',
-    top = 'top',
-    bottom = 'bottom',
-    rightKeyValue = 'rightKeyValue',
+    addRightKeyvalue = 'add:right:keyValue',
 }
 
 export interface DocFocusOptions {
@@ -115,7 +115,7 @@ export interface DocComponentView {
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
     restoreView?: (viewSpec: Doc) => boolean;
     scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
-    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void; // highlight a region of a view (used by freeforms)
+    brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
     getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
     addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
@@ -134,6 +134,7 @@ export interface DocComponentView {
     setFocus?: () => void; // sets input focus to the componentView
     setData?: (data: Field | Promise<RefField | undefined>) => boolean;
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+    dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
     incrementalRendering?: () => void;
     layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox)
     overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events)
@@ -141,7 +142,7 @@ export interface DocComponentView {
     annotationKey?: string;
     getTitle?: () => string;
     getCenter?: (xf: Transform) => { X: number; Y: number };
-    screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } };
+    screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
@@ -166,7 +167,6 @@ export interface DocumentViewSharedProps {
     childHideDecorationTitle?: () => boolean;
     childHideResizeHandles?: () => boolean;
     childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
-    dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
     styleProvider: Opt<StyleProviderFunc>;
     setTitleFocus?: () => void;
     focus: DocFocusFunc;
@@ -176,7 +176,7 @@ export interface DocumentViewSharedProps {
     searchFilterDocs: () => Doc[];
     layout_showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
-    rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
+    rootSelected: () => boolean; // whether the root of a template has been selected
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
@@ -191,6 +191,7 @@ export interface DocumentViewSharedProps {
     yPadding?: number;
     dropAction?: dropActionType;
     dontRegisterView?: boolean;
+    dragWhenActive?: boolean;
     hideLinkButton?: boolean;
     hideCaptions?: boolean;
     ignoreAutoHeight?: boolean;
@@ -234,13 +235,15 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     onPointerUp?: () => ScriptField;
     onBrowseClick?: () => ScriptField | undefined;
     onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+    dragStarting?: () => void;
+    dragEnding?: () => void;
 }
 
 // these props are only available in DocumentViewIntenral
 export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeWidth: () => number;
     NativeHeight: () => number;
-    isSelected: (outsideReaction?: boolean) => boolean;
+    isSelected: () => boolean;
     select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
     DocumentView: () => DocumentView;
     viewPath: () => DocumentView[];
@@ -261,8 +264,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
     private _dropDisposer?: DragManager.DragDropDisposer;
-    private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
@@ -320,7 +321,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
     @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
-        TraceMobx();
         return this._pointerEvents;
     }
     @computed get finalLayoutKey() {
@@ -333,7 +333,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this.props.NativeHeight();
     }
     @computed get disableClickScriptFunc() {
-        TraceMobx();
         const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
         return (
@@ -358,7 +357,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     componentWillUnmount() {
         this.cleanupHandlers(true);
     }
+    @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
+    @action
     componentDidMount() {
+        this._mounted = true;
         this.setupHandlers();
         this._disposers.contentActive = reaction(
             () => {
@@ -391,15 +393,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
             this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
-            this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
-            this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
         }
     }
     @action
     cleanupHandlers(unbrush: boolean) {
         this._dropDisposer?.();
-        this._multiTouchDisposer?.();
-        this._holdDisposer?.();
         unbrush && Doc.UnBrushDoc(this.props.Document);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
@@ -468,7 +466,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         }, console.log  );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
                 } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
-                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
+                    UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 } else {
@@ -568,7 +566,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
 
                 // listen to move events if document content isn't active or document is draggable
-                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive))) {
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive, this.props.dragWhenActive))) {
                     document.addEventListener('pointermove', this.onPointerMove);
                 }
             }
@@ -588,7 +586,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     cleanupPointerEvents = () => {
-        this.cleanUpInteractions();
         document.removeEventListener('pointermove', this.onPointerMove);
         document.removeEventListener('pointerup', this.onPointerUp);
     };
@@ -665,7 +662,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.addDocument(de.complete.linkDocument);
+                        this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -682,7 +679,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (!portalLink) {
             DocUtils.MakeLink(
                 this.props.Document,
-                Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _layout_fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }),
+                Docs.Create.FreeformDocument([], {
+                    _width: NumCast(this.layoutDoc._width) + 10,
+                    _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
+                    _isLightbox: true,
+                    _layout_fitWidth: true,
+                    title: StrCast(this.props.Document.title) + ' [Portal]',
+                }),
                 { link_relationship: 'portal to:portal from' }
             );
         }
@@ -735,7 +738,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected() && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -756,14 +759,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (!this.props.Document.isFolder) {
             const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null);
-            const appearance = cm.findByDescription('UI Controls...');
+            const appearance = cm.findByDescription('Appearance...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
 
             if (this.props.renderDepth === 0) {
-                appearanceItems.push({ description: 'Open in Lightbox', event: () => LightboxView.SetLightboxDoc(this.rootDoc), icon: 'hand-point-right' });
+                appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.rootDoc), icon: 'external-link-alt' });
             }
+            this.rootDoc.type === DocumentType.PRES && appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.rootDoc, {}), icon: 'eye' });
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
+            !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
 
             if (!Doc.IsSystem(this.rootDoc) && this.rootDoc.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.rootDoc._type_collection as any)) {
                 const existingOnClick = cm.findByDescription('OnClick...');
@@ -837,7 +841,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
         }
-        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'table-columns' });
+        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
         cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
 
         const help = cm.findByDescription('Help...');
@@ -894,13 +898,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         else cm.moveAfter(help);
 
         e?.stopPropagation(); // DocumentViews should stop propagation of this event
-        cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
+        cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, undefined);
     };
 
     @computed get _rootSelected() {
-        return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false;
+        return this.props.isSelected() || (this.props.Document.rootDocument && this.props.rootSelected?.()) || false;
     }
-    rootSelected = (outsideReaction?: boolean) => this._rootSelected;
+    rootSelected = () => this._rootSelected;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
@@ -927,12 +931,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get contents() {
         TraceMobx();
         const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
-        const noBackground = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
+        const noBackground = this.rootDoc._isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
         return (
             <div
                 className="documentView-contentsView"
                 style={{
-                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? 'all',
+                    pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
                     height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
                 }}>
                 <DocumentContentsView
@@ -1016,7 +1020,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     removeDocument={this.hideLink(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
-                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${Doc.LinkEndpoint(link, this.rootDoc)}`)}
+                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)}
                 />
             </div>
         ));
@@ -1160,7 +1164,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc;
         const background = StrCast(
             this.layoutDoc.layout_headingColor,
-            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(this.layoutDoc.layout_headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)))
+            StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
         );
         const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0;
         const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
@@ -1172,6 +1176,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
                 key="title"
                 style={{
+                    zIndex: 1,
                     position: this.headerMargin ? 'relative' : 'absolute',
                     height: this.titleHeight,
                     width: 100 - sidebarWidthPercent + '%',
@@ -1232,10 +1237,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.contents
         ) : (
             <div className="documentView-styleWrapper">
-                {' '}
-                {!this.headerMargin ? this.contents : titleView}
-                {!this.headerMargin ? titleView : this.contents}
-                {' ' /* */}
+                {titleView}
+                {this.contents}
                 {captionView}
             </div>
         );
@@ -1345,6 +1348,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+    @observable _selected = false;
+    public get SELECTED() {
+        return this._selected;
+    }
+    public set SELECTED(val) {
+        this._selected = val;
+    }
     @observable public static Interacting = false;
     @observable public static LongPress = false;
     @observable public static ExploreMode = false;
@@ -1354,9 +1364,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     @observable public docView: DocumentViewInternal | undefined | null;
     @observable public textHtmlOverlay: Opt<string>;
+    @observable public textHtmlOverlayTime: Opt<number>;
     @observable private _isHovering = false;
 
-    public htmlOverlayEffect = '';
+    public htmlOverlayEffect: Opt<Doc>;
     public get displayName() {
         return 'DocumentView(' + this.props.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
@@ -1408,7 +1419,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
         // prettier-ignore
         DocServer.GetRefField(docId).then(docx =>
-            LightboxView.SetLightboxDoc(
+            LightboxView.Instance.SetLightboxDoc(
                 (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
                 Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self, self.target)') }, docId)
             )
@@ -1485,7 +1496,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
     }
     @computed get panelHeight() {
-        if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.nativeHeightUnfrozen)) {
+        if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
             return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
         return this.props.PanelHeight();
@@ -1497,7 +1508,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.effectiveNativeWidth &&
             this.effectiveNativeHeight &&
             Math.abs(this.Xshift) < 0.001 &&
-            (!this.layoutDoc.nativeHeightUnfrozen || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
+            (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
             ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
             : 0;
     }
@@ -1508,18 +1519,22 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
     }
 
+    @computed get CollectionFreeFormView() {
+        return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
+    }
+    @computed get CollectionFreeFormDocumentView() {
+        return this.props.CollectionFreeFormDocumentView?.();
+    }
+
     public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     public getBounds = () => {
         if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
-        if (this.docView._componentView?.screenBounds) {
+        if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
-        const xf = this.docView.props
-            .ScreenToLocalTransform()
-            .scale(this.trueNativeWidth() ? this.nativeScaling : 1)
-            .inverse();
+        const xf = this.docView.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
         if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
@@ -1579,15 +1594,18 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
-    isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
+    isSelected = () => this.SELECTED;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
-        SelectionManager.SelectView(this, extendSelection);
-        if (focusSelection) {
-            DocumentManager.Instance.showDocument(this.rootDoc, {
-                willZoomCentered: true,
-                zoomScale: 0.9,
-                zoomTime: 500,
-            });
+        if (this.isSelected() && SelectionManager.Views().length > 1) SelectionManager.DeselectView(this);
+        else {
+            SelectionManager.SelectView(this, extendSelection);
+            if (focusSelection) {
+                DocumentManager.Instance.showDocument(this.rootDoc, {
+                    willZoomCentered: true,
+                    zoomScale: 0.9,
+                    zoomTime: 500,
+                });
+            }
         }
     };
     NativeWidth = () => this.effectiveNativeWidth;
@@ -1596,17 +1614,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
     selfView = () => this;
-    trueNativeWidth = () => returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, false));
     screenToLocalTransform = () =>
         this.props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
-            .scale(this.trueNativeWidth() ? 1 / this.nativeScaling : 1);
+            .scale(1 / this.nativeScaling);
+
+    @action
     componentDidMount() {
-        this._disposers.updateContentsScript = reaction(
-            () => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
-            output => output
-        );
+        this.rootDoc[DocViews].add(this);
+        this._disposers.updateContentsScript = reaction(() => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.rootDoc, self: this.rootDoc }).result, emptyFunction);
         this._disposers.height = reaction(
             // increase max auto height if document has been resized to be greater than current max
             () => NumCast(this.layoutDoc._height),
@@ -1617,23 +1634,36 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         );
         !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
     }
+    @action
     componentWillUnmount() {
+        this.rootDoc[DocViews].delete(this);
         Object.values(this._disposers).forEach(disposer => disposer?.());
         !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
+    // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
+    // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
+    // so we need to first activate the div, then, after a render timeout, start the opacity transition.
+    @observable enableHtmlOverlayTransitions: boolean = false;
     @computed get htmlOverlay() {
-        return !this.textHtmlOverlay ? null : (
-            <div className="documentView-htmlOverlay">
-                <div className="documentView-htmlOverlayInner">
-                    <Fade delay={0} duration={500}>
-                        {DocumentViewInternal.AnimationEffect(
-                            <div className="webBox-textHighlight">
-                                <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
-                            </div>,
-                            { presentation_effect: this.htmlOverlayEffect ?? 'Zoom' } as any as Doc,
-                            this.rootDoc
-                        )}{' '}
-                    </Fade>
+        const effect = StrCast(this.htmlOverlayEffect?.presentation_effect, StrCast(this.htmlOverlayEffect?.followLinkAnimEffect));
+        return (
+            <div
+                className="documentView-htmlOverlay"
+                ref={r => {
+                    const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
+                    if (r && val !== this.enableHtmlOverlayTransitions) {
+                        setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
+                    }
+                }}
+                style={{ display: !this.textHtmlOverlay ? 'none' : undefined }}>
+                <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
+                    {DocumentViewInternal.AnimationEffect(
+                        <div className="webBox-textHighlight">
+                            <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
+                        </div>,
+                        { ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
+                        this.rootDoc
+                    )}
                 </div>
             </div>
         );
@@ -1651,7 +1681,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
                         style={{
-                            transition: this.props.dataTransition,
+                            transition: 'inherit', // this.props.dataTransition,
                             transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height: this.props.forceAutoHeight
@@ -1689,8 +1719,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
 });
 
 ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
-    //documentView.iconify(() =>
-    LightboxView.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0);
+    LightboxView.Instance.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0);
 });
 
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
@@ -1700,7 +1729,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
 
 ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
     const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
-    let wid = linkSource[Width]();
+    let wid = NumCast(linkSource._width);
     let embedding: Doc | undefined;
     const links = LinkManager.Links(linkSource);
     links.forEach(link => {
@@ -1711,7 +1740,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
             embedding.x = wid;
             embedding.y = 0;
             embedding._lockedPosition = false;
-            wid += otherdoc[Width]();
+            wid += NumCast(otherdoc._width);
             Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding);
         }
     });
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index a77e4bdd1..d347c285b 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -2,7 +2,6 @@ import EquationEditor from 'equation-editor-react';
 import { action, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -68,7 +67,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         if (e.key === 'Tab') {
             const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], {
-                x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](),
+                x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width),
                 y: NumCast(this.layoutDoc.y),
                 _width: 400,
                 _height: 300,
@@ -85,8 +84,18 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     updateSize = () => {
         const style = this._ref.current && getComputedStyle(this._ref.current.element.current);
         if (style?.width.endsWith('px') && style?.height.endsWith('px')) {
-            this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', '')));
-            this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', '')));
+            if (this.layoutDoc._nativeWidth) {
+                // if equation has been scaled then editing the expression must also edit the native dimensions to keep the aspect ratio
+                const prevNwidth = NumCast(this.layoutDoc._nativeWidth);
+                const prevNheight = NumCast(this.layoutDoc._nativeHeight);
+                this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', '')));
+                this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', '')));
+                this.layoutDoc._width = (NumCast(this.layoutDoc._width) * NumCast(this.layoutDoc._nativeWidth)) / prevNwidth;
+                this.layoutDoc._height = (NumCast(this.layoutDoc._height) * NumCast(this.layoutDoc._nativeHeight)) / prevNheight;
+            } else {
+                this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', '')));
+                this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', '')));
+            }
         }
     };
     render() {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f014f842e..f7f94c546 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -21,7 +21,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     select: (isCtrlPressed: boolean) => void;
     isContentActive: (outsideReaction?: boolean) => boolean | undefined;
     isDocumentActive?: () => boolean | undefined;
-    isSelected: (outsideReaction?: boolean) => boolean;
+    isSelected: () => boolean;
     setHeight?: (height: number) => void;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
     onBrowseClick?: () => ScriptField | undefined;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index d2e1293da..de5ad1631 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -46,6 +46,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
+    //
+    // This controls whether fontIconButtons will display labels under their icons or not
+    //
+    public static get ShowIconLabels() {
+        return BoolCast(Doc.UserDoc()._showLabel);
+    }
+    public static set ShowIconLabels(show: boolean) {
+        Doc.UserDoc()._showLabel = show;
+    }
     @observable noTooltip = false;
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
@@ -161,7 +170,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     Doc.UnBrushAllDocs();
                 })}>
                 {this.Icon(color)}
-                {!this.label || !Doc.GetShowIconLabels() ? null : (
+                {!this.label || !FontIconBox.ShowIconLabels ? null : (
                     <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
                         {' '}
                         {this.label}{' '}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 29943e156..3ffda5a35 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -128,6 +128,7 @@
     right: 0;
     bottom: 0;
     z-index: 2;
+    cursor: default;
 }
 
 .imageBox-fader img {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 2f4f788d4..103843046 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { DocData, Width } from '../../../fields/DocSymbols';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -50,7 +50,6 @@ const uploadIcons = {
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
-    protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
@@ -71,6 +70,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
     private _overlayIconRef = React.createRef<HTMLDivElement>();
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
     @observable _uploadIcon = uploadIcons.idle;
 
@@ -85,7 +85,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
-        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
+        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor
         const anchor =
             visibleAnchor ??
             Docs.Create.ConfigDocument({
@@ -116,7 +116,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
         const layoutDoc = this.layoutDoc;
         this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }),
+            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
             ({ nativeSize, width }) => {
                 if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
@@ -240,7 +240,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.freeform_panY_max = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
-            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width]();
+            cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
             cropping.y = NumCast(this.rootDoc.y);
             this.props.addDocTab(cropping, OpenWhere.inParent);
         }
@@ -474,7 +474,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
-    @observable _marqueeing: number[] | undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
         TraceMobx();
@@ -488,7 +487,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
+                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
                     return true;
                 }),
                 returnFalse,
@@ -500,7 +499,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @action
     finishMarquee = () => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this.props.select(false);
     };
     focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
@@ -558,13 +557,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
-                {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                {!this._mainCont.current || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
                         rootDoc={this.rootDoc}
                         scrollTop={0}
-                        down={this._marqueeing}
-                        scaling={this.props.NativeDimScaling}
-                        docView={this.props.docViewPath().slice(-1)[0]}
+                        annotationLayerScrollTop={0}
+                        scaling={returnOne}
+                        annotationLayerScaling={this.props.NativeDimScaling}
+                        docView={this.props.DocumentView!}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index f22cb195f..6f99b7c28 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -7,7 +7,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
+import { OpenWhere, OpenWhereMod } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
@@ -50,7 +50,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, ((OpenWhere.addRight as string) + 'KeyValue') as OpenWhere), icon: 'layer-group' });
+            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     };
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index e66fed84b..416cb11cc 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,7 +2,6 @@ import React = require('react');
 import { Bezier } from 'bezier-js';
 import { computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
@@ -33,11 +32,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
     }
     screenBounds = () => {
-        if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+        if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
             const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
             const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
-            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) };
-            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) };
+            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
+            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) };
 
             const pts = [] as number[][];
             pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
@@ -51,23 +50,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
             );
             return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
         }
-        return { left: 0, top: 0, right: 0, bottom: 0, center: undefined };
+        return undefined;
     };
     disposer: IReactionDisposer | undefined;
     componentDidMount() {
         this.props.setContentView?.(this);
         this.disposer = reaction(
             () => {
-                if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) {
+                if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
                     const a = (this.anchor1 ?? this.anchor2)!;
                     const b = (this.anchor2 ?? this.anchor1)!;
 
                     const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
-                    const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
+                    const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform();
                     const a_invXf = a.props.ScreenToLocalTransform().inverse();
                     const b_invXf = b.props.ScreenToLocalTransform().inverse();
-                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) };
-                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) };
+                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
+                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
                     const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
 
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 198cbe851..7e4f1da8e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -3,13 +3,13 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import wiki from 'wikijs';
-import { Doc, DocCastAsync, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
+import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
@@ -19,7 +19,6 @@ import { Transform } from '../../util/Transform';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
-import { DocumentManager } from '../../util/DocumentManager';
 
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
@@ -56,21 +55,20 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         LinkDocPreview._instance = this;
     }
 
-    @action init() {
+    @action
+    init() {
         var linkTarget = this.props.linkDoc;
         this._linkSrc = this.props.linkSrc;
         this._linkDoc = this.props.linkDoc;
-        const link_anchor_1 = this._linkDoc?.link_anchor_1 as Doc;
-        const link_anchor_2 = this._linkDoc?.link_anchor_2 as Doc;
+        const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
+        const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
         if (link_anchor_1 && link_anchor_2) {
             linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1;
         }
         if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
-            // want to show annotation embedContainer document if annotation is not text
-            linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._markerTargetDoc = this._targetDoc = anno)));
-        } else {
-            this._markerTargetDoc = this._targetDoc = linkTarget;
+            linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text
         }
+        this._markerTargetDoc = this._targetDoc = linkTarget;
         this._toolTipText = '';
         this.updateHref();
     }
@@ -190,17 +188,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
 
     width = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height]();
+        if (NumCast(this._targetDoc._width) < NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._height)) * NumCast(this._targetDoc._width)) / NumCast(this._targetDoc._height);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Width](), 225));
+        return Math.min(225, NumCast(this._targetDoc._width, 225));
     };
     height = () => {
         if (!this._targetDoc) return 225;
-        if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) {
-            return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width]();
+        if (NumCast(this._targetDoc._width) > NumCast(this._targetDoc._height)) {
+            return (Math.min(225, NumCast(this._targetDoc._width)) * NumCast(this._targetDoc._height)) / NumCast(this._targetDoc._width);
         }
-        return Math.min(225, NumCast(this._targetDoc?.[Height](), 225));
+        return Math.min(225, NumCast(this._targetDoc._height, 225));
     };
     @computed get previewHeader() {
         return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index bdc074e0c..4bb0f14d2 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -1,4 +1,4 @@
-import { observable, runInAction } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
@@ -36,11 +36,28 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LoadingBox, fieldKey);
     }
+    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
+    // removes from currently loading display
+    @action
+    public static removeCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading) {
+            const index = LoadingBox.CurrentlyLoading.indexOf(doc);
+            index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+        }
+    }
+
+    // adds doc to currently loading display
+    @action
+    public static addCurrentlyLoading(doc: Doc) {
+        if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
+            LoadingBox.CurrentlyLoading.push(doc);
+        }
+    }
 
     _timer: any;
     @observable progress = '';
     componentDidMount() {
-        if (!Doc.CurrentlyLoading?.includes(this.rootDoc)) {
+        if (!LoadingBox.CurrentlyLoading?.includes(this.rootDoc)) {
             this.rootDoc.loadingError = 'Upload interrupted, please try again';
         } else {
             const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 7af4d9b59..f6680aac0 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -48,7 +48,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
-            selected => MapAnchorMenu.Instance.fadeOut(true)
+            sel => MapAnchorMenu.Instance.fadeOut(true)
         );
     }
     // audioDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 50b070e7f..9b75ca7e3 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -5,11 +5,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
-import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols';
+import { DocCss, Highlight } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkTool } from '../../../../fields/InkField';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -21,7 +20,6 @@ import { undoable, UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
-import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
@@ -67,14 +65,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return FieldView.LayoutString(MapBox, fieldKey);
     }
     private _dragRef = React.createRef<HTMLDivElement>();
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
-    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
 
-    @observable private _marqueeing: number[] | undefined;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
         return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -170,7 +165,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this.layoutDoc._layout_showSidebar = true;
@@ -278,28 +273,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
 
-    @action
-    onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-            setupMoveUpEvents(
-                this,
-                e,
-                action(e => {
-                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
-                    return true;
-                }),
-                returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
-            );
-        }
-    };
-    @action finishMarquee = (x?: number, y?: number) => {
-        this._marqueeing = undefined;
-        x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
-    };
-
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
     pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
@@ -307,8 +280,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()];
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -832,23 +805,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                     isAnyChildContentActive={this.isAnyChildContentActive}
                                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                                 /> */}
-
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <MarqueeAnnotator
-                            rootDoc={this.rootDoc}
-                            anchorMenuClick={this.anchorMenuClick}
-                            scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={returnOne}
-                            addDocument={this.addDocumentWrapper}
-                            docView={this.props.docViewPath().lastElement()}
-                            finishMarquee={this.finishMarquee}
-                            savedAnnotations={this.savedAnnotations}
-                            annotationLayer={this._annotationLayer.current}
-                            selectionText={returnEmptyString}
-                            mainCont={this._mainCont.current}
-                        />
-                    )}
                 </div>
                 {/* </LoadScript > */}
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 407a91dd0..6bad7d724 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -4,11 +4,9 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, runInAc
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
-import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -16,7 +14,6 @@ import { UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
-import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { Annotation } from '../../pdf/Annotation';
 import { SidebarAnnos } from '../../SidebarAnnos';
@@ -107,8 +104,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private selectedPlace: Doc | undefined;
     @observable private markerMap: { [id: string]: google.maps.Marker } = {};
     @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
-    @observable private _marqueeing: number[] | undefined;
-    @observable private _isAnnotating = false;
     @observable private inputRef = React.createRef<HTMLInputElement>();
     @observable private searchMarkers: google.maps.Marker[] = [];
     @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options);
@@ -120,7 +115,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return DocListCast(this.dataDoc[this.annotationKey]);
     }
     @observable private toggleAddMarker = false;
-    private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
     @observable _showSidebar = false;
     @computed get SidebarShown() {
@@ -368,7 +362,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         .ScreenToLocalTransform()
                         .scale(this.props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
-                    const fullWidth = this.layoutDoc[Width]();
+                    const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
                     if (this.sidebarWidth() + localDelta[0] > 0) {
                         this._showSidebar = true;
@@ -482,29 +476,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
 
-    @action
-    onMarqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-            setupMoveUpEvents(
-                this,
-                e,
-                action(e => {
-                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
-                    return true;
-                }),
-                returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
-                false
-            );
-        }
-    };
-    @action finishMarquee = (x?: number, y?: number) => {
-        this._marqueeing = undefined;
-        this._isAnnotating = false;
-        x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.props.Document);
-    };
-
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => {
         return this.addDocument(doc, annotationKey);
     };
@@ -547,8 +518,8 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()];
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this.props.PanelWidth() / 5;
     infoHeight = () => this.props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
@@ -599,22 +570,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             {/* {this.handleMapCenter(this._map)} */}
                         </GoogleMap>
                     </div>
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <MarqueeAnnotator
-                            rootDoc={this.rootDoc}
-                            anchorMenuClick={this.anchorMenuClick}
-                            scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={returnOne}
-                            addDocument={this.addDocumentWrapper}
-                            docView={this.props.docViewPath().lastElement()}
-                            finishMarquee={this.finishMarquee}
-                            savedAnnotations={this.savedAnnotations}
-                            annotationLayer={this._annotationLayer.current}
-                            selectionText={returnEmptyString}
-                            mainCont={this._mainCont.current}
-                        />
-                    )}
                 </div>
                 {/* </LoadScript > */}
                 <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c068d9dd7..108fa5ce5 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -63,7 +62,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         super(props);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
         if (this.pdfUrl) {
             if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)));
             else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
@@ -104,15 +103,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
-        newDiv.style.width = this.layoutDoc[Width]().toString();
-        newDiv.style.height = this.layoutDoc[Height]().toString();
+        newDiv.style.width = NumCast(this.layoutDoc._width).toString();
+        newDiv.style.height = NumCast(this.layoutDoc._height).toString();
         this.replaceCanvases(docViewContent, newDiv);
         const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1);
-        const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1);
+        const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1);
+        const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.rootDoc.title;
         cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width);
@@ -169,8 +168,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             CollectionFreeFormView.UpdateIcon(
                 filename,
                 docViewContent,
-                this.layoutDoc[Width](),
-                this.layoutDoc[Height](),
+                NumCast(this.layoutDoc._width),
+                NumCast(this.layoutDoc._height),
                 this.props.PanelWidth(),
                 this.props.PanelHeight(),
                 NumCast(this.layoutDoc._layout_scrollTop),
@@ -196,7 +195,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             () => this.props.isSelected(),
             () => {
                 document.removeEventListener('keydown', this.onKeyDown);
-                this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown);
+                this.props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
             },
             { fireImmediately: true }
         );
@@ -255,8 +254,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.dataDoc[this.props.fieldKey + '_numPages'] = np;
         Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
         Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
-        this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1);
-        !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw));
+        this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
+        !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
     };
 
     public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => {
@@ -331,7 +330,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -348,15 +347,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     toggleSidebar = action((preview: boolean = false) => {
         const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
-        const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
+        const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + NumCast(this.layoutDoc._width)) / NumCast(this.layoutDoc._width);
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth;
         }
     });
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index ebb8a3374..b2512a0e5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,6 @@ import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DocCast, NumCast } from '../../../fields/Types';
@@ -18,16 +17,16 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { CaptureManager } from '../../util/CaptureManager';
 import { SettingsManager } from '../../util/SettingsManager';
+import { TrackMovements } from '../../util/TrackMovements';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { media_state } from './AudioBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
-import { TrackMovements } from '../../util/TrackMovements';
-import { media_state } from './AudioBox';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -145,7 +144,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         if (!nativeWidth || !nativeHeight) {
             if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200);
             Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect);
-            this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect;
+            this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect;
         }
     };
 
@@ -295,7 +294,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
         this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
     };
-    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth();
+    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 7c8a1849e..197c520c7 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -27,7 +27,6 @@ const _global = (window /* browser */ || global) /* node */ as any;
 @observer
 export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
     private dropDisposer?: DragManager.DragDropDisposer;
-    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldStr: string) {
         return FieldView.LayoutString(ScriptingBox, fieldStr);
     }
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f803715ad..f90c19050 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -100,6 +100,7 @@
     padding: 0 10px 0 7px;
     transition: opacity 0.3s;
     z-index: 10001;
+    transform-origin: top left;
 
     .timecode-controls {
         display: flex;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index d7f7c9b73..8bf2f4ce5 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import { Doc, StrListCast } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -65,6 +64,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _youtubeIframeId: number = -1;
     private _youtubeContentCreated = false;
     private _audioPlayer: HTMLAudioElement | null = null;
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _playRegionTimer: any = null; // timeout for playback
@@ -72,7 +72,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @observable _stackedTimeline: any; // CollectionStackedTimeline ref
     @observable static _nativeControls: boolean; // default html controls
-    @observable _marqueeing: number[] | undefined; // coords for marquee selection
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
     @observable _clicking = false; // used for transition between showing/hiding timeline
@@ -163,7 +162,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (
             // need to include range inputs because after dragging time slider it becomes target element
             !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
-            this.props.isSelected(true)
+            this.props.isSelected()
         ) {
             switch (e.key) {
                 case 'ArrowLeft':
@@ -355,8 +354,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     updateIcon = () => {
         const makeIcon = (returnedfilename: string) => {
             this.dataDoc.icon = new ImageField(returnedfilename);
-            this.dataDoc.icon_nativeWidth = this.layoutDoc[Width]();
-            this.dataDoc.icon_nativeHeight = this.layoutDoc[Height]();
+            this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width);
+            this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height);
         };
         this.Snapshot(undefined, undefined, makeIcon);
     };
@@ -627,7 +626,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
             );
             this._disposers.youtubeReactionDisposer = reaction(
-                () => Doc.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
+                () => Doc.ActiveTool === InkTool.None && this.props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
                 interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
                 { fireImmediately: true }
             );
@@ -868,7 +867,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
+                    this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
                     return true;
                 }),
                 returnFalse,
@@ -882,7 +881,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // ends marquee selection
     @action
     finishMarquee = () => {
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this.props.select(true);
     };
 
@@ -913,7 +912,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             .scale(100 / this.heightPercent);
     };
 
-    marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100;
     marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0];
 
     timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`];
@@ -938,8 +936,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <div
                     className="videoBox-ui"
                     style={{
-                        transformOrigin: 'top left',
-                        transform: `rotate(${NumCast(this.rootDoc.rotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
@@ -953,6 +950,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
     };
 
+    thumbnails = () => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']);
     // renders CollectionStackedTimeline
     @computed get renderTimeline() {
         return (
@@ -964,7 +962,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     fieldKey={this.annotationKey}
                     dictationKey={this.fieldKey + '_dictation'}
                     mediaPath={this.audiopath}
-                    thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])}
+                    thumbnails={this.thumbnails}
                     renderDepth={this.props.renderDepth + 1}
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
@@ -1096,13 +1094,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
+                            ref={this._marqueeref}
                             rootDoc={this.rootDoc}
                             scrollTop={0}
-                            down={this._marqueeing}
-                            scaling={this.marqueeFitScaling}
-                            docView={this.props.docViewPath().slice(-1)[0]}
+                            annotationLayerScrollTop={0}
+                            scaling={returnOne}
+                            annotationLayerScaling={this.props.NativeDimScaling}
+                            docView={this.props.DocumentView!}
                             containerOffset={this.marqueeOffset}
                             addDocument={this.addDocWithTimecode}
                             finishMarquee={this.finishMarquee}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2aca314da..e42fb4a03 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,9 +1,8 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
-import { Height, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { HtmlField } from '../../../fields/HtmlField';
 import { InkTool } from '../../../fields/InkField';
@@ -52,6 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _outerRef: React.RefObject<HTMLDivElement> = React.createRef();
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _keyInput = React.createRef<HTMLInputElement>();
@@ -67,10 +67,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _searching: boolean = false;
     @observable private _showSidebar = false;
     @observable private _webPageHasBeenRendered = false;
-    @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private _marqueeing: number[] | undefined;
-    @observable private _isAnnotating = false;
-    @observable private _iframeClick: HTMLIFrameElement | undefined = undefined;
+    get marqueeing() {
+        return this._marqueeing;
+    }
+    set marqueeing(val) {
+        val && this._marqueeref.current?.onInitiateSelection(val);
+        !val && this._marqueeref.current?.onTerminateSelection();
+        this._marqueeing = val;
+    }
     @observable private _iframe: HTMLIFrameElement | null = null;
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight);
@@ -81,7 +86,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this._url ? WebBox.urlHash(this._url) + '' : '';
     }
     @computed get scrollHeight() {
-        return Math.max(this.layoutDoc[Height](), this._scrollHeight);
+        return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
     }
     @computed get allAnnotations() {
         return DocListCast(this.dataDoc[this.annotationKey]);
@@ -209,7 +214,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
                     if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600);
                     Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect);
-                    this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect;
+                    this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
@@ -246,6 +251,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
+            if (this.rootDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -262,12 +268,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1);
                 }
             }
+            this._mainCont.current.style.transform = '';
         }
-        //this._selectionText = selRange.cloneContents().textContent || "";
         this._selectionContent = selRange?.cloneContents();
         this._selectionText = this._selectionContent?.textContent || '';
 
         // clear selection
+        this._textAnnotationCreator = undefined;
         if (sel.empty) sel.empty(); // Chrome
         else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
         return this._savedAnnotations;
@@ -276,7 +283,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (anchor !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[Height](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[Height](), this._scrollHeight));
+            const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
                     const focusTime = options.zoomTime ?? 500;
@@ -315,7 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ele.append(contents);
             }
         } catch (e) {}
-        const visibleAnchor = this._getAnchor(this._savedAnnotations, false);
+        const visibleAnchor = this._getAnchor(this._savedAnnotations, true);
         const anchor =
             visibleAnchor ??
             Docs.Create.ConfigDocument({
@@ -335,6 +342,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     iframeUp = (e: PointerEvent) => {
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         this._textAnnotationCreator = undefined;
         this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
@@ -354,26 +362,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     @action
     webClipDown = (e: React.PointerEvent) => {
-        const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
+        e.stopPropagation();
+        const sel = window.getSelection();
+        this._textAnnotationCreator = undefined;
+        if (sel?.empty) sel.empty(); // Chrome
+        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        // bcz: NEED TO unrotate e.clientX and e.clientY
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
         this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-        e.button !== 2 && (this._marqueeing = [e.clientX, e.clientY]);
-        if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
-            e.stopPropagation();
-            setTimeout(
-                action(() => (this._marqueeing = undefined)),
-                100
-            ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-        } else {
-            this._isAnnotating = true;
-            this.props.select(false);
-            e.stopPropagation();
+
+        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
+            if (e.button !== 2) this.marqueeing = [e.clientX, e.clientY];
             e.preventDefault();
         }
         document.addEventListener('pointerup', this.webClipUp);
     };
+    @action
     webClipUp = (e: PointerEvent) => {
+        if (window.getSelection()?.isCollapsed && this._marqueeref.current?.isEmpty) {
+            this.marqueeing = undefined;
+        }
         document.removeEventListener('pointerup', this.webClipUp);
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         const sel = window.getSelection();
@@ -382,7 +391,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this._selectionText = sel.toString();
             AnchorMenu.Instance.setSelectedText(sel.toString());
             this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
-            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
+            (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
             // Changing which document to add the annotation to (the currently selected WebBox)
             GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
             GPTPopup.Instance.addDoc = this.sidebarAddDocument;
@@ -390,36 +399,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     @action
     iframeDown = (e: PointerEvent) => {
-        const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
-        const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
-        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
-        this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc);
+        this.props.select(false);
+        const locpt = { 
+            x: (e.clientX / NumCast(this.rootDoc.nativeWidth)) * this.props.PanelWidth(), 
+            y: ((e.clientY - NumCast(this.rootDoc.layout_scrollTop))/ NumCast(this.rootDoc.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
+        const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+        const scrcent = this.props
+            .DocumentView?.()
+            .props.ScreenToLocalTransform()
+            .inverse()
+            .transformPoint(NumCast(this.rootDoc.width) / 2, NumCast(this.rootDoc.height) / 2)!;
+        const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate);
+        const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]];
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-        e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale]);
-        if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
-            setTimeout(
-                action(() => (this._marqueeing = undefined)),
-                100
-            ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
-        } else {
-            this._iframeClick = this._iframe ?? undefined;
-            this._isAnnotating = true;
-            this.props.select(false);
-            e.stopPropagation();
-            e.preventDefault();
-        }
-
-        // bcz: hack - iframe grabs all events which messes up how we handle contextMenus.  So this super naively simulates the event stack to get the specific menu items and the doc view menu items.
-        if (e.button === 2 || (e.button === 0 && e.altKey)) {
+        const word = getWordAtPoint(e.target, e.clientX, e.clientY);
+        if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) {
+            this.marqueeing = theclick;
             e.preventDefault();
-            //e.stopPropagation();
-            ContextMenu.Instance.closeMenu();
-            ContextMenu.Instance.setIgnoreEvents(true);
         }
     };
     isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
-    iframeClick = () => this._iframeClick;
-    iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale;
 
     addWebStyleSheet(document: any, styleType: string = 'text/css') {
         if (document) {
@@ -490,7 +489,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
                 if (this._scrollHeight) {
                     this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
-                    this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
                 }
             };
             const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
@@ -498,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth);
                 this.rootDoc.nativeWidth = swidth;
                 this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize;
-                this.layoutDoc.height = this.layoutDoc[Height]() * aspectResize;
+                this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
             }
             initHeights();
             this._iframetimeout && clearTimeout(this._iframetimeout);
@@ -708,10 +707,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 icon: 'snowflake',
             });
             funcs.push({
-                description: (!this.layoutDoc.layout_forceReflow ? 'Force' : 'Prevent') + ' Reflow',
+                description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
                 event: () => {
-                    const nw = !this.layoutDoc.layout_forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
-                    this.layoutDoc.layout_forceReflow = !nw;
+                    const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
+                    this.layoutDoc.layout_reflowHorizontal = !nw;
                     if (nw) {
                         Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true);
                     }
@@ -723,38 +722,56 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
+    /**
+     * This gets called when some other child of the webbox is selected and a pointer down occurs on the webbox.
+     * it's also called for html clippings when you click outside the bounds of the clipping
+     * @param e
+     */
     @action
     onMarqueeDown = (e: React.PointerEvent) => {
+        const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
+        this._textAnnotationCreator = undefined;
+        if (sel?.empty) sel.empty(); // Chrome
+        else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        this.marqueeing = [e.clientX, e.clientY];
         if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
                 action(e => {
                     MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-                    this._marqueeing = [e.clientX, e.clientY];
                     return true;
                 }),
                 returnFalse,
-                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
+                action(() => {
+                    this.marqueeing = undefined;
+                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
+                }),
                 false
             );
+        } else {
+            this.marqueeing = undefined;
         }
     };
     @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
-        this._marqueeing = undefined;
-        this._isAnnotating = false;
-        this._iframeClick = undefined;
+        this.marqueeing = undefined;
+        this._textAnnotationCreator = undefined;
         const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
         if (sel?.empty) sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
+        this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.rootDoc);
         if (x !== undefined && y !== undefined) {
-            this._setPreviewCursor?.(x, y, false, false, this.rootDoc);
             ContextMenu.Instance.closeMenu();
             ContextMenu.Instance.setIgnoreEvents(false);
             if (e?.button === 2 || e?.altKey) {
-                this.specificContextMenu(undefined as any);
-                this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                e?.preventDefault();
+                e?.stopPropagation();
+                setTimeout(() => {
+                    // if menu comes up right away, the down event can still be active causing a menu item to be selected
+                    this.specificContextMenu(undefined as any);
+                    this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                });
             }
         }
     };
@@ -762,6 +779,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable lighttext = false;
 
     @computed get urlContent() {
+        if (this.props.ScreenToLocalTransform().Scale > 25) return <div></div>;
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -796,7 +814,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     key={this._warning}
                     className="webBox-iframe"
                     ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
-                    style={{ pointerEvents: this._isAnyChildContentActive || DocumentView.Interacting ? 'none' : undefined }}
+                    style={{ pointerEvents: DocumentView.Interacting ? 'none' : undefined }}
                     src={url}
                     onLoad={this.iframeLoaded}
                     scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
@@ -838,7 +856,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
-                    onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]);
+                    onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
                     this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth;
                 }
                 return false;
@@ -873,24 +891,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     toggleSidebar = action((preview: boolean = false) => {
         var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         if (!nativeWidth) {
-            const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width]();
+            const defaultNativeWidth = NumCast(this.rootDoc.nativeWidth, this.rootDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width));
             Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth);
-            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / this.Document[Width]()) * defaultNativeWidth);
+            Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth);
             nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         }
         const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth;
-        const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth;
+        const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + NumCast(this.layoutDoc.width)) / NumCast(this.layoutDoc.width);
         const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
         if (preview) {
             this._previewNativeWidth = nativeWidth * sideratio;
-            this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth;
+            this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth;
             this._showSidebar = true;
         } else {
             this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar;
-            this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth;
+            this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth;
             if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) {
                 this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined;
             } else {
+                !this.layoutDoc._layout_showSidebar && (this.dataDoc[this.fieldKey + '_nativeWidth'] = this.dataDoc[this.fieldKey + '_nativeHeight'] = undefined);
                 this.layoutDoc.nativeWidth = nativeWidth * pdfratio;
             }
         }
@@ -918,7 +937,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 className={'webBox-cont' + (interactive ? '-interactive' : '')}
                 onKeyDown={e => e.stopPropagation()}
                 style={{
-                    width: !this.layoutDoc.layout_forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%',
+                    width: !this.layoutDoc.layout_reflowHorizontal ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%',
                     transform: `scale(${this.zoomScaling()})  translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`,
                 }}>
                 {this._hackHide ? null : this.urlContent}
@@ -940,7 +959,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
-                        <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                        <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
         );
@@ -1002,7 +1021,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 onWheel={this.onZoomWheel}
                 onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight) || '100%', pointerEvents }}>
+                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this.props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                     {this.content}
                     <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
                     {this.renderOpaqueAnnotations}
@@ -1047,14 +1066,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
-    showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.IsOpaqueFilter()])];
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
@@ -1065,7 +1083,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
             ? 'all' //
             : 'none';
-    annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
+    annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
@@ -1088,27 +1106,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     }}
                     onContextMenu={this.specificContextMenu}>
                     {this.webpage}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
-                        <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}>
-                            <MarqueeAnnotator
-                                rootDoc={this.rootDoc}
-                                iframe={this.isFirefox() ? this.iframeClick : undefined}
-                                iframeScaling={this.isFirefox() ? this.iframeScaling : undefined}
-                                anchorMenuClick={this.anchorMenuClick}
-                                scrollTop={0}
-                                down={this._marqueeing}
-                                scaling={returnOne}
-                                addDocument={this.addDocumentWrapper}
-                                docView={this.props.docViewPath().lastElement()}
-                                finishMarquee={this.finishMarquee}
-                                savedAnnotations={this.savedAnnotationsCreator}
-                                selectionText={this.selectionText}
-                                annotationLayer={this._annotationLayer.current}
-                                mainCont={this._mainCont.current}
-                            />
-                        </div>
-                    )}
                 </div>
+                {!this._mainCont.current || !this._annotationLayer.current ? null : (
+                    <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
+                        <MarqueeAnnotator
+                            ref={this._marqueeref}
+                            rootDoc={this.rootDoc}
+                            anchorMenuClick={this.anchorMenuClick}
+                            scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
+                            annotationLayerScrollTop={0}
+                            scaling={this.props.NativeDimScaling}
+                            addDocument={this.addDocumentWrapper}
+                            docView={this.props.DocumentView!}
+                            finishMarquee={this.finishMarquee}
+                            savedAnnotations={this.savedAnnotationsCreator}
+                            selectionText={this.selectionText}
+                            annotationLayer={this._annotationLayer.current}
+                            mainCont={this._mainCont.current}
+                        />
+                    </div>
+                )}
                 <div
                     className="webBox-sideResizer"
                     style={{
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 425ef3e54..914adb404 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -2,7 +2,7 @@
  *
  * @param {StyleSheetList} styleSheets
  */
-var ForeignHtmlRenderer = function (styleSheets) {
+const ForeignHtmlRenderer = function (styleSheets) {
     const self = this;
 
     /**
@@ -10,7 +10,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @param {String} binStr
      */
     const binaryStringToBase64 = function (binStr) {
-        return new Promise(function (resolve) {
+        return new Promise(resolve => {
             const reader = new FileReader();
             reader.readAsDataURL(binStr);
             reader.onloadend = function () {
@@ -31,17 +31,17 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @returns {Promise}
      */
     const getResourceAsBase64 = function (webUrl, inurl) {
-        return new Promise(function (resolve, reject) {
+        return new Promise((resolve, reject) => {
             const xhr = new XMLHttpRequest();
-            //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl;
-            //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl;
-            var url = inurl;
+            // const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl;
+            // const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl;
+            let url = inurl;
             if (inurl.startsWith('/static')) {
                 url = new URL(webUrl).origin + inurl;
             } else if (inurl.startsWith('/') && !inurl.startsWith('//')) {
                 url = CorsProxy(new URL(webUrl).origin + inurl);
             } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) {
-                url = CorsProxy(webUrl + '/' + inurl);
+                url = CorsProxy(`${webUrl}/${inurl}`);
             } else if (inurl.startsWith('https') && !inurl.startsWith(window.location.origin)) {
                 url = CorsProxy(inurl);
             }
@@ -57,7 +57,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
                         resourceBase64: resBase64,
                     });
                 } else if (xhr.readyState === XMLHttpRequest.DONE) {
-                    console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl));
+                    console.log(`COULDN'T FIND: ${inurl.startsWith('/') ? webUrl + inurl : inurl}`);
                     resolve({
                         resourceUrl: '',
                         resourceBase64: inurl,
@@ -76,7 +76,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      */
     const getMultipleResourcesAsBase64 = function (webUrl, urls) {
         const promises = [];
-        for (let i = 0; i < urls.length; i++) {
+        for (let i = 0; i < urls.length; i += 1) {
             promises.push(getResourceAsBase64(webUrl, urls[i]));
         }
         return Promise.all(promises);
@@ -98,7 +98,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
         }
 
         let val = '';
-        for (let i = idx + prefixToken.length; i < str.length; i++) {
+        for (let i = idx + prefixToken.length; i < str.length; i += 1) {
             if (suffixTokens.indexOf(str[i]) !== -1) {
                 break;
             }
@@ -112,6 +112,15 @@ var ForeignHtmlRenderer = function (styleSheets) {
         };
     };
 
+    /**
+     *
+     * @param {String} str
+     * @returns {String}
+     */
+    const removeQuotes = function (str) {
+        return str.replace(/["']/g, '');
+    };
+
     /**
      *
      * @param {String} cssRuleStr
@@ -127,13 +136,12 @@ var ForeignHtmlRenderer = function (styleSheets) {
                 break;
             }
             searchStartIndex = url.foundAtIndex + url.value.length + 1;
-            if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue;
-            const unquoted = removeQuotes(url.value);
-            if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') {
-                continue;
+            if (!mustEndWithQuote || url.value[url.value.length - 1] === '"') {
+                const unquoted = removeQuotes(url.value);
+                if (unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") ) */ && unquoted !== 'http://' && unquoted !== 'https://') {
+                    if (unquoted) urlsFound.push(unquoted);
+                }
             }
-
-            unquoted && urlsFound.push(unquoted);
         }
 
         return urlsFound;
@@ -151,15 +159,6 @@ var ForeignHtmlRenderer = function (styleSheets) {
         return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true);
     };
 
-    /**
-     *
-     * @param {String} str
-     * @returns {String}
-     */
-    const removeQuotes = function (str) {
-        return str.replace(/["']/g, '');
-    };
-
     const escapeRegExp = function (string) {
         return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
     };
@@ -172,8 +171,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
      *
      * @returns {Promise<String>}
      */
-    const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) {
-        return new Promise(async function (resolve, reject) {
+    const buildSvgDataUri = async function (webUrl, inputContentHtml, width, height, scroll, xoff) {
+        return new Promise(async (resolve, reject) => {
             /* !! The problems !!
              *  1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
              *  2. Platform won't wait for external assets to load (fonts, images, etc.)
@@ -181,17 +180,19 @@ var ForeignHtmlRenderer = function (styleSheets) {
 
             // copy styles
             let cssStyles = '';
-            let urlsFoundInCss = [];
+            const urlsFoundInCss = [];
 
-            for (let i = 0; i < styleSheets.length; i++) {
+            for (let i = 0; i < styleSheets.length; i += 1) {
                 try {
                     const rules = styleSheets[i].cssRules;
-                    for (let j = 0; j < rules.length; j++) {
+                    for (let j = 0; j < rules.length; j += 1) {
                         const cssRuleStr = rules[j].cssText;
                         urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
                         cssStyles += cssRuleStr;
                     }
-                } catch (e) {}
+                } catch (e) {
+                    /* empty */
+                }
             }
 
             // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
@@ -202,15 +203,15 @@ var ForeignHtmlRenderer = function (styleSheets) {
             //     }
             // }
 
-            contentHtml = contentHtml
+            let contentHtml = inputContentHtml
                 .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs.  instead of converting each, just use the default <img> of the picture
                 .replace(/noscript/g, 'div')
                 .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s.  But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
                 .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
                 .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
-            let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
+            const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
             const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
-            for (let i = 0; i < fetchedResources.length; i++) {
+            for (let i = 0; i < fetchedResources.length; i += 1) {
                 const r = fetchedResources[i];
                 if (r.resourceUrl) {
                     contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), 'g'), r.resourceBase64);
@@ -230,7 +231,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
             // contentRootElem.style.transform = "scale(0.08)"
             contentRootElem.innerHTML = styleElemString + contentHtml;
             contentRootElem.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
-            //document.body.appendChild(contentRootElem);
+            // document.body.appendChild(contentRootElem);
 
             const contentRootElemString = new XMLSerializer().serializeToString(contentRootElem);
 
@@ -256,13 +257,13 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @return {Promise<Image>}
      */
     this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
-        return new Promise(async function (resolve, reject) {
+        return new Promise(async (resolve, reject) => {
             const img = new Image();
-            console.log('BUILDING SVG for:' + webUrl);
+            console.log(`BUILDING SVG for: ${webUrl}`);
             img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
 
             img.onload = function () {
-                console.log('IMAGE SVG created:' + webUrl);
+                console.log(`IMAGE SVG created: ${webUrl}`);
                 resolve(img);
             };
         });
@@ -276,7 +277,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @return {Promise<Image>}
      */
     this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
-        return new Promise(async function (resolve, reject) {
+        return new Promise(async (resolve, reject) => {
             const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff);
 
             const canvas = document.createElement('canvas');
@@ -298,7 +299,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @return {Promise<String>}
      */
     this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
-        return new Promise(async function (resolve, reject) {
+        return new Promise(async (resolve, reject) => {
             const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample);
             resolve(canvas.toDataURL('image/png'));
         });
@@ -320,8 +321,8 @@ export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xo
     );
 }
 
-var ClipboardUtils = new (function () {
-    var permissions = {
+const ClipboardUtils = new (function () {
+    const permissions = {
         'image/bmp': true,
         'image/gif': true,
         'image/png': true,
@@ -330,8 +331,8 @@ var ClipboardUtils = new (function () {
     };
 
     function getType(types) {
-        for (var j = 0; j < types.length; ++j) {
-            var type = types[j];
+        for (let j = 0; j < types.length; j += 1) {
+            const type = types[j];
             if (permissions[type]) {
                 return type;
             }
@@ -339,10 +340,10 @@ var ClipboardUtils = new (function () {
         return null;
     }
     function getItem(items) {
-        for (var i = 0; i < items.length; ++i) {
-            var item = items[i];
+        for (let i = 0; i < items.length; i += 1) {
+            const item = items[i];
             if (item) {
-                var type = getType(item.types);
+                const type = getType(item.types);
                 if (type) {
                     return item.getType(type);
                 }
@@ -352,7 +353,7 @@ var ClipboardUtils = new (function () {
     }
     function loadFile(file, callback) {
         if (window.FileReader) {
-            var reader = new FileReader();
+            const reader = new FileReader();
             reader.onload = function () {
                 callback(reader.result, null);
             };
@@ -366,23 +367,23 @@ var ClipboardUtils = new (function () {
     }
     this.readImage = function (callback) {
         if (navigator.clipboard) {
-            var promise = navigator.clipboard.read();
+            const promise = navigator.clipboard.read();
             promise
-                .then(function (items) {
-                    var promise = getItem(items);
-                    if (promise == null) {
+                .then(items => {
+                    const promise2 = getItem(items);
+                    if (promise2 == null) {
                         callback(null, null);
                         return;
                     }
-                    promise
-                        .then(function (result) {
+                    promise2
+                        .then(result => {
                             loadFile(result, callback);
                         })
-                        .catch(function (error) {
+                        .catch(error => {
                             callback(null, 'Reading clipboard error.');
                         });
                 })
-                .catch(function (error) {
+                .catch(error => {
                     callback(null, 'Reading clipboard error.');
                 });
         } else {
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index c5167461b..e8b9a98b7 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnTrue, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
@@ -177,6 +177,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     };
 
     componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
+    isContentActive = () => this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.();
 
     render() {
         return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : (
@@ -190,6 +191,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     display: 'inline-block',
                     left: 0,
                     top: 0,
+                    pointerEvents: this.isContentActive() ? undefined : 'none',
                 }}
                 onPointerLeave={this.onPointerLeave}
                 onPointerEnter={this.onPointerEnter}
@@ -203,7 +205,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     rootSelected={returnFalse} //{this._textBox.props.isSelected}
                     removeDocument={this.removeDoc}
                     isDocumentActive={returnFalse}
-                    isContentActive={emptyFunction}
+                    isContentActive={this.isContentActive}
                     styleProvider={this._textBox.props.styleProvider}
                     docViewPath={this._textBox.props.docViewPath}
                     ScreenToLocalTransform={this.getDocTransform}
@@ -213,7 +215,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     PanelWidth={this._finalLayout[Width]}
                     PanelHeight={this._finalLayout[Height]}
                     focus={this.outerFocus}
-                    whenChildContentsActiveChanged={returnFalse}
+                    whenChildContentsActiveChanged={this.props.tbox.whenChildContentsActiveChanged}
                     bringToFront={emptyFunction}
                     dontRegisterView={false}
                     childFilters={this.props.tbox?.props.childFilters}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index d5ad128fe..0a64c8062 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -177,6 +177,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 style={{
                     width: this.props.width,
                     height: this.props.height,
+                    pointerEvents: this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
                 {this.props.hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 818c0cbe7..717efd5a9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -49,6 +49,7 @@ audiotag:hover {
     transition: opacity 1s;
     width: 100%;
     position: relative;
+    transform-origin: left top;
     top: 0;
     left: 0;
 }
@@ -89,6 +90,7 @@ audiotag:hover {
     bottom: 0;
     width: 11;
     height: 11;
+    cursor: default;
 }
 
 .formattedTextBox-outer {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 41b1c59b0..b65c440d1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,6 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
+import { setCORS } from 'google-translate-api-browser';
 import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -52,6 +53,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
+import { media_state } from '../AudioBox';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
@@ -71,8 +73,6 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
-import { media_state } from '../AudioBox';
-import { setCORS } from 'google-translate-api-browser';
 // setting up cors-anywhere server address
 const translate = setCORS('http://cors-anywhere.herokuapp.com/');
 export const GoogleRef = 'googleDocId';
@@ -93,6 +93,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     static _bulletStyleSheet: any = addStyleSheet();
     static _userStyleSheet: any = addStyleSheet();
     static _hadSelection: boolean = false;
+    private _selectionHTML: string | undefined;
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _sidebarTagRef = React.createRef<React.Component>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
@@ -315,7 +316,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
-        this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
+        this.props.isSelected() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
+        let ele: Opt<HTMLDivElement> = undefined;
+        try {
+            const contents = window.getSelection()?.getRangeAt(0).cloneContents();
+            if (contents) {
+                ele = document.createElement('div');
+                ele.append(contents);
+            }
+            this._selectionHTML = ele?.innerHTML;
+        } catch (e) {}
     };
 
     dispatchTransaction = (tx: Transaction) => {
@@ -434,10 +444,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             var tr = this._editorView.state.tr as any;
             const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor;
             tr = tr.removeMark(0, tr.doc.content.size, autoAnch);
-            DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
+            Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks)));
             tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t)));
             this._editorView?.dispatch(tr);
-            // this.prepareForTyping();
         }
         oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink);
     };
@@ -460,7 +469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             if (!(cfield instanceof ComputedField)) {
                 this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim();
                 if (str.startsWith('@') && str.length > 1) {
-                    Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc);
+                    Doc.AddToMyPublished(this.rootDoc);
                 }
             }
         }
@@ -487,9 +496,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                 ) ||
                                     DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
+                            // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
                             const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
-                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+                            const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
                         }
                     });
@@ -579,8 +589,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     // embed document when drag marked as embed
                 } else if (de.embedKey) {
                     const node = schema.nodes.dashDoc.create({
-                        width: draggedDoc[Width](),
-                        height: draggedDoc[Height](),
+                        width: NumCast(draggedDoc._width),
+                        height: NumCast(draggedDoc._height),
                         title: 'dashDoc',
                         docId: draggedDoc[Id],
                         float: 'unset',
@@ -704,14 +714,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @action
     toggleSidebar = (preview: boolean = false) => {
+        const defaultSidebar = 250;
         const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
         if (preview) this._showSidebar = true;
         else {
             this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
-            this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%';
+            this.layoutDoc._layout_showSidebar =
+                (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%';
         }
 
-        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
+        this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) + defaultSidebar : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth);
     };
     sidebarDown = (e: React.PointerEvent) => {
         const batch = UndoManager.StartBatch('toggle sidebar');
@@ -733,7 +745,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(this.props.NativeDimScaling?.() || 1)
             .transformDirection(delta[0], delta[1]);
         const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
-        const width = this.layoutDoc[Width]() + localDelta[0];
+        const width = NumCast(this.layoutDoc._width) + localDelta[0];
         this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%';
         this.layoutDoc.width = width;
         this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
@@ -853,29 +865,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format',
             })
         );
+        const appearance = cm.findByDescription('Appearance...');
+        const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
 
-        const uicontrols: ContextMenuProps[] = [];
-        uicontrols.push({
+        appearanceItems.push({
             description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle',
             event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar),
             icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye',
         });
-        uicontrols.push({
+        appearanceItems.push({
             description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI',
             event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI),
             icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
         });
-        !Doc.noviceMode && uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
+        !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
-            uicontrols.push({
+            appearanceItems.push({
                 description: 'Broadcast Message',
                 event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)),
                 icon: 'expand-arrows-alt',
             });
-        cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' });
 
-        const appearance = cm.findByDescription('Appearance...');
-        const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' });
         // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
 
@@ -909,7 +919,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 },
                 icon: 'eye',
             });
-        cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+        !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
 
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
@@ -1054,7 +1064,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter));
                 this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false;
                 anchor.text = selectedText;
+                anchor.text_html = this._selectionHTML ?? selectedText;
                 anchor.title = selectedText.substring(0, 30);
+                anchor.presentation_zoomText = true;
                 return anchor;
             }
             return anchorDoc ?? this.rootDoc;
@@ -1407,7 +1419,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [
                             view.state.schema.marks.linkAnchor.create({
                                 allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }],
-                                location: 'add:right',
                                 title: `from: ${DocCast(pdfAnchor.embedContainer).title}`,
                                 noPreview: true,
                                 docref: false,
@@ -1440,7 +1451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const self = this;
         return new Plugin({
             view(newView) {
-                runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+                runInAction(() => self.props.isSelected() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
                 return new RichTextMenuPlugin({ editorProps: this.props });
             },
         });
@@ -1613,7 +1624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._downX = e.clientX;
         this._downY = e.clientY;
         FormattedTextBoxComment.textBox = this;
-        if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && (this.props.rootSelected() || this.props.isSelected()) && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
                 // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
                 e.stopPropagation(); // if the text box's content is active, then it consumes all down events
@@ -1642,15 +1653,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
             while (target && !target.dataset?.targethrefs) target = target.parentElement;
             FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
-            if (pcords && pcords.inside > 0 && state.doc.nodeAt(pcords.inside)?.type === state.schema.nodes.dashDoc) {
-                return;
-            }
         }
     };
     @action
     onDoubleClick = (e: React.MouseEvent): void => {
         FormattedTextBoxComment.textBox = this;
-        if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
                 // stop propagation if not in sidebar
                 e.stopPropagation(); // if the text box is selected, then it consumes all click events
@@ -1661,7 +1669,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         FormattedTextBoxComment.Hide();
 
-        if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
+        if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
             e.stopPropagation();
         }
     };
@@ -1707,7 +1715,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
             }
         }
-        if (this.props.isSelected(true)) {
+        if (this.props.isSelected()) {
             // if text box is selected, then it consumes all click events
             (e.nativeEvent as any).handledByInnerReactInstance = true;
             this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
@@ -1721,7 +1729,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
         let olistPos = clickPos?.pos;
-        if (clickPos && olistPos && this.props.isSelected(true)) {
+        if (clickPos && olistPos && this.props.isSelected()) {
             const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
             const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
             olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
@@ -1770,7 +1778,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 tr && this._editorView.dispatch(tr);
             }
         }
-        if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) {
+        if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected()) {
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
         }
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
@@ -1937,7 +1945,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const color = !annotated ? Colors.WHITE : Colors.BLACK;
         const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
 
-        return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : (
+        return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
             <div
                 className="formattedTextBox-sidebar-handle"
                 onPointerDown={this.sidebarDown}
@@ -2050,7 +2058,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
     @computed get fieldKey() {
         const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}_usePath`]);
-        return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : '');
+        return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this.props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
     }
     @observable _isHovering = false;
     onPassiveWheel = (e: WheelEvent) => {
@@ -2067,7 +2075,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (this.props.isContentActive()) {
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
-                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight()) && //
+                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1)) && //
                 (this.props.isSelected() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index e3ac4fb9d..712be91c8 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -181,7 +181,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveAlignment() {
-        if (this.view && this.TextView?.props.isSelected(true)) {
+        if (this.view && this.TextView?.props.isSelected()) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
                 if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
@@ -194,7 +194,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveListStyle() {
-        if (this.view && this.TextView?.props.isSelected(true)) {
+        if (this.view && this.TextView?.props.isSelected()) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = 0; i < path.length; i += 3) {
                 if (path[i].type === this.view.state.schema.nodes.ordered_list) {
@@ -214,7 +214,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const activeSizes = new Set<string>();
         const activeColors = new Set<string>();
         const activeHighlights = new Set<string>();
-        if (this.view && this.TextView?.props.isSelected(true)) {
+        if (this.view && this.TextView?.props.isSelected()) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
             const marks: Mark[] = [...(state.storedMarks ?? [])];
@@ -249,7 +249,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
         let activeMarks: MarkType[] = [];
-        if (!this.view || !this.TextView?.props.isSelected(true)) return activeMarks;
+        if (!this.view || !this.TextView?.props.isSelected()) return activeMarks;
 
         const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
         if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
@@ -286,7 +286,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     destroy() {
-        !this.TextView?.props.isSelected(true) && this.fadeOut(true);
+        !this.TextView?.props.isSelected() && this.fadeOut(true);
     }
 
     @action
@@ -443,7 +443,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
-        if (this.TextView?.props.isSelected(true)) {
+        if (this.TextView?.props.isSelected()) {
             var tr = view.state.tr;
             view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
                 if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 6f07588b3..4faef26e2 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -28,7 +28,6 @@ export const marks: { [index: string]: MarkSpec } = {
     autoLinkAnchor: {
         attrs: {
             allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
-            location: { default: null },
             title: { default: null },
         },
         inclusive: false,
@@ -37,7 +36,6 @@ export const marks: { [index: string]: MarkSpec } = {
                 tag: 'a[href]',
                 getAttrs(dom: any) {
                     return {
-                        location: dom.getAttribute('location'),
                         title: dom.getAttribute('title'),
                     };
                 },
@@ -46,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = {
         toDOM(node: any) {
             const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
             const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
-            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-noPreview': 'true', 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
+            return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
         },
     },
     noAutoLinkAnchor: {
@@ -73,7 +71,6 @@ export const marks: { [index: string]: MarkSpec } = {
     linkAnchor: {
         attrs: {
             allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] },
-            location: { default: null },
             title: { default: null },
             noPreview: { default: false },
             docref: { default: false }, // flags whether the linked text comes from a document within Dash.  If so, an attribution label is appended after the text
@@ -84,7 +81,6 @@ export const marks: { [index: string]: MarkSpec } = {
                 tag: 'a[href]',
                 getAttrs(dom: any) {
                     return {
-                        location: dom.getAttribute('location'),
                         title: dom.getAttribute('title'),
                         noPreview: dom.getAttribute('noPreview'),
                     };
@@ -108,7 +104,7 @@ export const marks: { [index: string]: MarkSpec } = {
                           node.attrs.title,
                       ],
                   ]
-                : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline; cursor: default` }, 0];
+                : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
         },
     },
 
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index f27fb18e2..4cd2cee52 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -212,7 +212,6 @@ export const nodes: { [index: string]: NodeSpec } = {
             alt: { default: null },
             title: { default: null },
             float: { default: 'left' },
-            location: { default: 'add:right' },
             docId: { default: '' },
         },
         group: 'inline',
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3d7c68bcd..789509784 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -27,7 +27,6 @@ import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
-import { TabDocView } from '../../collections/TabDocView';
 import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -204,16 +203,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
         this._disposers.editing = reaction(
             () => this.layoutDoc.presentation_status === PresStatus.Edit,
-            editing => {
-                if (editing) {
-                    this.childDocs.forEach(doc => {
-                        if (doc.presentation_indexed !== undefined) {
-                            this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
-                            doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
-                        }
-                    });
-                }
-            }
+            editing => editing && 
+                this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => {
+                    this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+                    doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
+                }) // prettier-ignore
         );
     }
 
@@ -283,19 +277,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     // go to documents chain
-    runSubroutines = (childrenToRun: Doc[], normallyNextSlide: Doc) => {
-        console.log(childrenToRun, normallyNextSlide, 'runSUBFUNC');
-        if (childrenToRun === undefined) {
-            console.log('children undefined');
-            return;
+    runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => {
+        if (childrenToRun && childrenToRun[0] !== normallyNextSlide) {
+            childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {}));
         }
-        if (childrenToRun[0] === normallyNextSlide) {
-            return;
-        }
-
-        childrenToRun.forEach(child => {
-            DocumentManager.Instance.showDocument(child, {});
-        });
     };
 
     // Called when the user activates 'next' - to move to the next part of the pres. trail
@@ -453,7 +438,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
             const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
             if (context) {
-                const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+                const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
                 if (ffview?.childDocs) {
                     PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
                     ffview.rootDoc._currentFrame = NumCast(activeFrame);
@@ -626,7 +611,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     changed = true;
                     const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
                     activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
-                    dv.ComponentView?.brushView?.(viewport, transTime);
+                    dv.ComponentView?.brushView?.(viewport, transTime, 2500);
                 }
             } else {
                 if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
@@ -782,6 +767,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
     };
 
+    public static PanelName = 'PRESBOX';
+
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
         if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
             (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
@@ -796,7 +783,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
             effect: activeItem,
             noSelect: true,
-            openLocation: OpenWhere.addLeft,
+            openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
             anchorDoc: activeItem,
             easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
             zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
@@ -806,7 +793,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (activeItem.presentation_openInLightbox) {
             const context = DocCast(targetDoc.annotationOn) ?? targetDoc;
             if (!DocumentManager.Instance.getLightboxDocumentView(context)) {
-                LightboxView.SetLightboxDoc(context);
+                LightboxView.Instance.SetLightboxDoc(context);
             }
         }
         if (targetDoc) {
@@ -815,7 +802,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
                 // if target or the doc it annotates is not in the lightbox, then close the lightbox
                 if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
-                    LightboxView.SetLightboxDoc(undefined);
+                    LightboxView.Instance.SetLightboxDoc(undefined);
                 }
                 DocumentManager.Instance.showDocument(targetDoc, options, finished);
             });
@@ -909,7 +896,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             break;
                     }
                 });
-            LightboxView.SetLightboxDoc(undefined);
+            LightboxView.Instance.SetLightboxDoc(undefined);
             Doc.RemFromMyOverlay(this.rootDoc);
             return PresStatus.Edit;
         };
@@ -1082,7 +1069,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
                     audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
                     audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
-                    TabDocView.PinDoc(audio, { audioRange: true });
+                    this.props.pinToPres(audio, { audioRange: true });
                     setTimeout(() => this.removeDocument(doc), 0);
                     return false;
                 }
@@ -1653,8 +1640,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 let dataField = Doc.LayoutFieldKey(tagDoc);
                                 if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';
 
-                                if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn["${dataField}"]`);
-                                else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc["${dataField}"]`);
+                                if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn?.["${dataField}"]`);
+                                else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc?.["${dataField}"]`);
                             }}
                             checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
                         />
@@ -2237,7 +2224,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const config_data = Cast(this.rootDoc.data, listSpec(Doc));
             if (data && config_data) {
                 data.push(doc);
-                TabDocView.PinDoc(doc, {});
+                this.props.pinToPres(doc, {});
                 this.gotoDocument(this.childDocs.length, this.activeItem);
             } else {
                 this.props.addDocTab(doc, OpenWhere.addRight);
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 82ed9e8d5..c667f126f 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -3,7 +3,6 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
-import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -15,6 +14,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, undoBatch } from '../../../util/UndoManager';
+import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -25,9 +25,6 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
-import { TreeView } from '../../collections/TreeView';
-import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
-import { MultiToggle, Type } from 'browndash-components';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -331,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @computed get recordingIsInOverlay() {
-        return DocListCast(Doc.MyOverlayDocs.data).some(doc => doc.slides === this.rootDoc);
+        return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc);
     }
 
     // a previously recorded video will have timecode defined
@@ -341,14 +338,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        DocListCast(Doc.MyOverlayDocs.data)
-            .filter(doc => doc.slides === this.rootDoc)
-            .forEach(Doc.RemFromMyOverlay);
+        Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
         // Remove every recording that already exists in overlay view
-        DocListCast(Doc.MyOverlayDocs.data).forEach(doc => {
+        Doc.MyOverlayDocs.forEach(doc => {
             if (doc.slides !== null) {
                 // if it's a recording video, don't remove from overlay (user can lose data)
                 if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) {
@@ -407,8 +402,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             activeItem.recording = recording;
 
             // make recording box appear in the bottom right corner of the screen
-            recording.overlayX = window.innerWidth - recording[Width]() - 20;
-            recording.overlayY = window.innerHeight - recording[Height]() - 20;
+            recording.overlayX = window.innerWidth - NumCast(recording._width) - 20;
+            recording.overlayY = window.innerHeight - NumCast(recording._height) - 20;
             Doc.AddToMyOverlay(recording);
         }
     };
@@ -551,7 +546,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
         return (
             <div
-                className={`presItem-container`}
+                className='presItem-container'
                 key={this.props.Document[Id] + this.indexInPres}
                 ref={this._itemRef}
                 style={{
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 18cf633f4..d68859e88 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -64,7 +64,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
-            selected => AnchorMenu.Instance.fadeOut(true)
+            sel => AnchorMenu.Instance.fadeOut(true)
         );
     }
 
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 52904b852..e1e87763c 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -12,19 +12,20 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
 import { LinkManager } from '../../util/LinkManager';
+import { Rect } from 'react-measure';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
     dataDoc: Doc;
     fieldKey: string;
-    showInfo: (anno: Opt<Doc>) => void;
+    showInfo?: (anno: Opt<Doc>) => void;
     pointerEvents?: () => Opt<string>;
 }
 @observer
 export class Annotation extends React.Component<IAnnotationProps> {
     render() {
         return (
-            <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}>
+            <div style={{ display: this.props.anno.textCopied && !Doc.GetBrushHighlightStatus(this.props.anno) ? 'none' : undefined }}>
                 {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
                     <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
                 ))}
@@ -69,18 +70,24 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         }
     };
 
+    @action
+    onContextMenu = (e: React.MouseEvent) => {
+        AnchorMenu.Instance.Status = 'annotation';
+        AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
+        AnchorMenu.Instance.Pinned = false;
+        AnchorMenu.Instance.PinToPres = this.pinToPres;
+        AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
+        AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
+        AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
+        AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+        e.stopPropagation();
+        e.preventDefault();
+    };
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (e.button === 2 || e.ctrlKey) {
-            AnchorMenu.Instance.Status = 'annotation';
-            AnchorMenu.Instance.Delete = this.deleteAnnotation.bind(this);
-            AnchorMenu.Instance.Pinned = false;
-            AnchorMenu.Instance.PinToPres = this.pinToPres;
-            AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle;
-            AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler;
-            AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.annoTextRegion);
-            AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true);
             e.stopPropagation();
+            e.preventDefault();
         } else if (e.button === 0) {
             e.stopPropagation();
             LinkFollower.FollowLink(undefined, this.annoTextRegion, false);
@@ -90,25 +97,26 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     @computed get linkHighlighted() {
         for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) {
             const a1 = LinkManager.getOppositeAnchor(link, this.props.document);
-            if (a1 && Doc.IsBrushedDegreeUnmemoized(DocCast(a1.annotationOn, this.props.document))) return true;
+            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this.props.document))) return true;
         }
     }
 
     render() {
-        const brushed = this.annoTextRegion && Doc.isBrushedHighlightedDegree(this.annoTextRegion);
+        const brushed = this.annoTextRegion && Doc.GetBrushHighlightStatus(this.annoTextRegion);
         return (
             <div
                 className="htmlAnnotation"
                 ref={this._mainCont}
                 onPointerEnter={action(() => {
                     Doc.BrushDoc(this.props.anno);
-                    this.props.showInfo(this.props.anno);
+                    this.props.showInfo?.(this.props.anno);
                 })}
                 onPointerLeave={action(() => {
                     Doc.UnBrushDoc(this.props.anno);
-                    this.props.showInfo(undefined);
+                    this.props.showInfo?.(undefined);
                 })}
                 onPointerDown={this.onPointerDown}
+                onContextMenu={this.onContextMenu}
                 style={{
                     left: NumCast(this.props.document.x),
                     top: NumCast(this.props.document.y),
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 038c45582..5392ec88e 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -211,6 +211,31 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
         );
     };
 
+    data = () => {
+        return (
+            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+                {this.heading('GENERATED IMAGE')}
+                <div className="image-content-wrapper">
+                    {this.imgUrls.map(rawSrc => (
+                        <div className="img-wrapper">
+                            <div className="img-container">
+                                <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+                            </div>
+                            <div className="btn-container">
+                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+                            </div>
+                        </div>
+                    ))}
+                </div>
+                {!this.loading && (
+                    <>
+                        <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+                    </>
+                )}
+            </div>
+        );
+    };
+
     summaryBox = () => (
         <>
             <div>
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 43b662f0f..ca9bf7bd2 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
-import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Height } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -11,7 +11,6 @@ import { TraceMobx } from '../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
-import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -56,16 +55,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
     @observable private _pageSizes: { width: number; height: number }[] = [];
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _marqueeing: number[] | undefined;
     @observable private _textSelecting = true;
     @observable private _showWaiting = true;
-    @observable private _overlayAnnoInfo: Opt<Doc>;
     @observable private Index: number = -1;
 
     private _pdfViewer: any;
     private _styleRule: any; // stylesheet rule for making hyperlinks clickable
     private _retries = 0; // number of times tried to create the PDF viewer
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+    private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -110,13 +108,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
         this._disposers.selected = reaction(
             () => this.props.isSelected(),
-            selected => {
-                // if (!selected) {
-                //     Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove()));
-                //     Array.from(this._savedAnnotations.keys()).forEach(k => this._savedAnnotations.set(k, []));
-                // }
-                SelectionManager.Views().length === 1 && this.setupPdfJsViewer();
-            },
+            selected => SelectionManager.Views().length === 1 && this.setupPdfJsViewer(),
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
@@ -374,17 +366,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             this.props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
-            this._marqueeing = [e.clientX, e.clientY];
+            this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
             this.isAnnotating = true;
             const target = e.target as any;
             if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) {
                 this._textSelecting = false;
             } else {
                 // if textLayer is hit, then we select text instead of using a marquee so clear out the marquee.
-                setTimeout(
-                    action(() => (this._marqueeing = undefined)),
-                    100
-                ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
+                setTimeout(() => this._marqueeref.current?.onTerminateSelection(), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
 
                 this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' });
                 document.addEventListener('pointerup', this.onSelectEnd);
@@ -396,12 +385,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
     finishMarquee = (x?: number, y?: number) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
-        this._marqueeing = undefined;
+        this._marqueeref.current?.onTerminateSelection();
         this._textSelecting = true;
     };
 
     @action
     onSelectEnd = (e: PointerEvent): void => {
+        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         clearStyleSheetRules(PDFViewer._annotationStyle);
         this.props.select(false);
@@ -425,23 +415,26 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
+            this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
-                if (rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
+                if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
+                    const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
                     const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
                     // transforms the positions from screen onto the pdf div
-                    annoBox.style.top = (((rect.top - boundingRect.top) * scaleX) / pdfScale + this._mainCont.current.scrollTop).toString();
                     annoBox.style.left = (((rect.left - boundingRect.left) * scaleX) / pdfScale).toString();
-                    annoBox.style.width = ((rect.width * this._mainCont.current.offsetWidth) / boundingRect.width / pdfScale).toString();
-                    annoBox.style.height = ((rect.height * this._mainCont.current.offsetHeight) / boundingRect.height / pdfScale).toString();
+                    annoBox.style.top = (((rect.top - boundingRect.top) * scaleY) / pdfScale + this._mainCont.current.scrollTop).toString();
+                    annoBox.style.width = ((rect.width * scaleX) / pdfScale).toString();
+                    annoBox.style.height = ((rect.height * scaleY) / pdfScale).toString();
                     this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top));
                 }
             }
+            this._mainCont.current!.style.transform = '';
         }
         this._selectionContent = selRange.cloneContents();
         this._selectionText = this._selectionContent?.textContent || '';
@@ -487,30 +480,19 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return (
             <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
-                    <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                    <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
         );
     }
 
-    @computed get overlayInfo() {
-        return !this._overlayAnnoInfo ? null : (
-            <div className="pdfViewerDash-overlayAnno" style={{ top: NumCast(this._overlayAnnoInfo.y), left: NumCast(this._overlayAnnoInfo.x) }}>
-                <div className="pdfViewerDash-overlayAnno" style={{ right: -50, background: SharingManager.Instance.users.find(users => users.user.email === this._overlayAnnoInfo!.author)?.userColor }}>
-                    {this._overlayAnnoInfo.author + ' ' + Field.toString(this._overlayAnnoInfo.author_date as Field)}
-                </div>
-            </div>
-        );
-    }
-
     getScrollHeight = () => this._scrollHeight;
-    showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])];
+    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
@@ -572,6 +554,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
     }
     savedAnnotations = () => this._savedAnnotations;
+    addDocumentWrapper = (doc: Doc | Doc[]) => this.props.addDocument!(doc);
     render() {
         TraceMobx();
         return (
@@ -590,17 +573,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
                     {this.overlayLayer}
-                    {this.overlayInfo}
                     {this._showWaiting ? <img className="pdfViewerDash-waiting" src={'/assets/loading.gif'} /> : null}
-                    {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
+                    {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
+                            ref={this._marqueeref}
                             rootDoc={this.props.rootDoc}
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this.props.anchorMenuClick}
                             scrollTop={0}
-                            down={this._marqueeing}
-                            addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)}
-                            docView={this.props.docViewPath().lastElement()}
+                            annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+                            addDocument={this.addDocumentWrapper}
+                            docView={this.props.DocumentView!}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotations}
                             selectionText={this.selectionText}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index feacdc9c5..e91fcd858 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,44 +1,23 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
 import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
-import { DocumentType } from '../client/documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
 import { LinkManager } from '../client/util/LinkManager';
 import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
 import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
 import { undoable } from '../client/util/UndoManager';
+import { DocumentView } from '../client/views/nodes/DocumentView';
 import { decycle } from '../decycler/decycler';
 import * as JSZipUtils from '../JSZipUtils';
-import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils';
+import { incrementTitleCopy, Utils } from '../Utils';
 import { DateField } from './DateField';
 import {
-    AclAdmin,
-    AclAugment,
-    AclEdit,
-    AclPrivate,
-    AclReadonly,
-    Animation,
-    AudioPlay,
-    CachedUpdates,
-    DirectLinks,
-    DocAcl,
-    DocCss,
-    DocData,
-    DocFields,
-    DocLayout,
-    FieldKeys,
-    FieldTuples,
-    ForceServerWrite,
-    Height,
-    Highlight,
-    Initializing,
-    Self,
-    SelfProxy,
-    UpdatingFromServer,
-    Width,
-} from './DocSymbols';
+    AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
+    DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
+    Initializing, Self, SelfProxy, UpdatingFromServer, Width
+} from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
 import { InkField, InkTool } from './InkField';
 import { List, ListFieldName } from './List';
@@ -50,7 +29,7 @@ import { listSpec } from './Schema';
 import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, TraceMobx } from './util';
+import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
 import JSZip = require('jszip');
 export const LinkedTo = '-linkedTo';
 export namespace Field {
@@ -63,25 +42,23 @@ export namespace Field {
                 : ''
             : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field));
     }
-    export function toScriptString(field: Field): string {
+    export function toScriptString(field: Field) {
         switch (typeof field) {
-            case 'string':
-                if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
-                return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
+            case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
+                           return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
             case 'number':
-            case 'boolean':
-                return String(field);
-        }
-        return field?.[ToScriptString]?.() ?? 'null';
+            case 'boolean':return String(field);
+            default:       return field?.[ToScriptString]?.() ?? 'null';
+        } // prettier-ignore
     }
-    export function toString(field: Field): string {
+    export function toString(field: Field) {
         if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
         return field?.[ToString]?.() || '';
     }
     export function IsField(field: any): field is Field;
     export function IsField(field: any, includeUndefined: true): field is Field | undefined;
     export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
-        return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
+        return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
     }
     export function Copy(field: any) {
         return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
@@ -104,11 +81,6 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
     const list = Cast(field, listSpec(Doc));
     return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
 }
-
-export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
-    return Cast(field, Doc);
-}
-
 export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
     return Cast(field, listSpec('number'), defaultVal);
 }
@@ -159,140 +131,51 @@ export function updateCachedAcls(doc: Doc) {
 @Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
     @observable public static RecordingEvent = 0;
-
-    // this isn't really used at the moment, but is intended to indicate whether ink stroke are passed through a gesture recognizer
-    static GetRecognizeGestures() {
-        return BoolCast(Doc.UserDoc()._recognizeGestures);
-    }
-    static SetRecognizeGestures(show: boolean) {
-        Doc.UserDoc()._recognizeGestures = show;
-    }
-
-    //
-    // This controls whether fontIconButtons will display labels under their icons or not
-    //
-    static GetShowIconLabels() {
-        return BoolCast(Doc.UserDoc()._showLabel);
-    }
-    static SetShowIconLabels(show: boolean) {
-        Doc.UserDoc()._showLabel = show;
-    }
-    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
-    // removes from currently loading display
-    @action
-    public static removeCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading) {
-            const index = Doc.CurrentlyLoading.indexOf(doc);
-            index !== -1 && Doc.CurrentlyLoading.splice(index, 1);
-        }
-    }
-
-    // adds doc to currently loading display
-    @action
-    public static addCurrentlyLoading(doc: Doc) {
-        if (Doc.CurrentlyLoading.indexOf(doc) === -1) {
-            Doc.CurrentlyLoading.push(doc);
-        }
-    }
-
     @observable public static GuestDashboard: Doc | undefined;
     @observable public static GuestTarget: Doc | undefined;
     @observable public static GuestMobile: Doc | undefined;
-    public static get MySharedDocs() {
-        return DocCast(Doc.UserDoc().mySharedDocs);
-    }
-    public static get MyUserDocView() {
-        return DocCast(Doc.UserDoc().myUserDocView);
-    }
-    public static get MyDockedBtns() {
-        return DocCast(Doc.UserDoc().myDockedBtns);
-    }
-    public static get MySearcher() {
-        return DocCast(Doc.UserDoc().mySearcher);
-    }
-    public static get MyHeaderBar() {
-        return DocCast(Doc.UserDoc().myHeaderBar);
-    }
-    public static get MyLeftSidebarMenu() {
-        return DocCast(Doc.UserDoc().myLeftSidebarMenu);
-    }
-    public static get MyLeftSidebarPanel() {
-        return DocCast(Doc.UserDoc().myLeftSidebarPanel);
-    }
-    public static get MyContextMenuBtns() {
-        return DocCast(Doc.UserDoc().myContextMenuBtns);
-    }
-    public static get MyTopBarBtns() {
-        return DocCast(Doc.UserDoc().myTopBarBtns);
-    }
-    public static get MyRecentlyClosed() {
-        return DocCast(Doc.UserDoc().myRecentlyClosed);
-    }
-    public static get MyTrails() {
-        return DocCast(Doc.ActiveDashboard?.myTrails);
-    }
-    public static IsInMyOverlay(doc: Doc) {
-        return DocListCast(Doc.MyOverlayDocs?.data).includes(doc);
-    }
-    public static AddToMyOverlay(doc: Doc) {
-        Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static RemFromMyOverlay(doc: Doc) {
-        Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc);
-    }
-    public static get MyOverlayDocs() {
-        return DocCast(Doc.UserDoc().myOverlayDocs);
-    }
-    public static get MyPublishedDocs() {
-        return DocCast(Doc.UserDoc().myPublishedDocs);
-    }
-    public static get MyDashboards() {
-        return DocCast(Doc.UserDoc().myDashboards);
-    }
-    public static get MyTemplates() {
-        return DocCast(Doc.UserDoc().myTemplates);
-    }
-    public static get MyImports() {
-        return DocCast(Doc.UserDoc().myImports);
-    }
-    public static get MyFilesystem() {
-        return DocCast(Doc.UserDoc().myFilesystem);
-    }
-    public static get MyTools() {
-        return DocCast(Doc.UserDoc().myTools);
-    }
-    public static get ActivePage() {
-        return StrCast(Doc.UserDoc().activePage);
-    }
-    public static set ActivePage(val) {
-        Doc.UserDoc().activePage = val;
-        DocServer.UPDATE_SERVER_CACHE();
-    }
-    public static IsComicStyle(doc?: Doc) {
-        return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
-    }
-    public static get ActiveDashboard() {
-        return DocCast(Doc.UserDoc().activeDashboard);
-    }
-    public static set ActiveDashboard(val: Doc | undefined) {
-        const overlays = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), null);
-        overlays && (overlays.length = 0);
-        Doc.UserDoc().activeDashboard = val;
-    }
-    public static set ActiveTool(tool: InkTool) {
-        Doc.UserDoc().activeTool = tool;
-    }
-    public static get ActiveTool(): InkTool {
-        return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;
-    }
-    public static get ActivePresentation(): Opt<Doc> {
-        return DocCast(Doc.ActiveDashboard?.activePresentation);
-    }
-    public static set ActivePresentation(val) {
-        if (Doc.ActiveDashboard) {
-            Doc.ActiveDashboard.activePresentation = val;
-        }
-    }
+    public static CurrentUserEmail: string = '';
+
+    public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
+    public static get MyUserDocView()         { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
+    public static get MyDockedBtns()          { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore
+    public static get MySearcher()            { return DocCast(Doc.UserDoc().mySearcher); } // prettier-ignore
+    public static get MyHeaderBar()           { return DocCast(Doc.UserDoc().myHeaderBar); } // prettier-ignore
+    public static get MyLeftSidebarMenu()     { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } // prettier-ignore
+    public static get MyLeftSidebarPanel()    { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } // prettier-ignore
+    public static get MyContextMenuBtns()     { return DocCast(Doc.UserDoc().myContextMenuBtns); } // prettier-ignore
+    public static get MyTopBarBtns()          { return DocCast(Doc.UserDoc().myTopBarBtns); } // prettier-ignore
+    public static get MyRecentlyClosed()      { return DocCast(Doc.UserDoc().myRecentlyClosed); } // prettier-ignore
+    public static get MyTrails()              { return DocCast(Doc.ActiveDashboard?.myTrails); } // prettier-ignore
+    public static get MyOverlayDocs()         { return DocListCast(Doc.ActiveDashboard?.myOverlayDocs ?? DocCast(Doc.UserDoc().myOverlayDocs).data); } // prettier-ignore
+    public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs ?? DocCast(Doc.UserDoc().myPublishedDocs).data); } // prettier-ignore
+    public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
+    public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
+    public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
+    public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
+    public static get MyTools()               { return DocCast(Doc.UserDoc().myTools); } // prettier-ignore
+    public static get noviceMode()            { return BoolCast(Doc.UserDoc().noviceMode);  } // prettier-ignore
+    public static set noviceMode(val)         { Doc.UserDoc().noviceMode = val; } // prettier-ignore
+    public static get IsSharingEnabled()      { return BoolCast(Doc.UserDoc().isSharingEnabled); } // prettier-ignore
+    public static set IsSharingEnabled(val)   { Doc.UserDoc().isSharingEnabled = val; } // prettier-ignore
+    public static get defaultAclPrivate()     { return Doc.UserDoc().defaultAclPrivate; } // prettier-ignore
+    public static set defaultAclPrivate(val)  { Doc.UserDoc().defaultAclPrivate = val; } // prettier-ignore
+    public static get ActivePage()            { return StrCast(Doc.UserDoc().activePage); } // prettier-ignore
+    public static set ActivePage(val)         { Doc.UserDoc().activePage = val; } // prettier-ignore
+    public static get ActiveTool(): InkTool   { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;  } // prettier-ignore
+    public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore
+    public static get ActivePresentation()    { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>;  } // prettier-ignore
+    public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore
+    public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore
+    public static set ActiveDashboard(val: Opt<Doc>) {  Doc.UserDoc().activeDashboard = val; } // prettier-ignore
+
+    public static IsInMyOverlay(doc: Doc)     { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore
+    public static AddToMyOverlay(doc: Doc)    { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyOverlay(doc: Doc)  { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+    public static AddToMyPublished(doc: Doc)  { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore
+    public static IsComicStyle(doc?: Doc)     { return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic' ; } // prettier-ignore
+
     constructor(id?: FieldId, forceSave?: boolean) {
         super(id);
         const docProxy = new Proxy<this>(this, {
@@ -354,17 +237,16 @@ export class Doc extends RefField {
     @observable public [AudioPlay]: any; // meant to store sound object from Howl
     @observable public [Animation]: Opt<Doc>;
     @observable public [Highlight]: boolean = false;
-    static __Anim(Doc: Doc) {
-        // for debugging to print AnimationSym field easily.
-        return Doc[Animation];
-    }
+    @observable public [Brushed]: boolean = false;
+    @observable public [DocViews] = new ObservableSet<DocumentView>();
 
+    private [Self] = this;
+    private [SelfProxy]: any;
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
-    public [Initializing]: boolean = false;
+    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
 
-    private [Self] = this;
-    private [SelfProxy]: any;
+    public [Initializing]: boolean = false;
     public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
         if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
             DocServer.UpdateField(this[Id], serverOp);
@@ -375,9 +257,7 @@ export class Doc extends RefField {
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
     public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
-    public get [DocLayout]() {
-        return this[SelfProxy].__LAYOUT__;
-    }
+    public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
     public get [DocData](): Doc {
         const self = this[SelfProxy];
         return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self);
@@ -398,29 +278,6 @@ export class Doc extends RefField {
         return undefined;
     }
 
-    private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
-    public static get noviceMode() {
-        return Doc.UserDoc().noviceMode as boolean;
-    }
-    public static set noviceMode(val) {
-        Doc.UserDoc().noviceMode = val;
-    }
-    public static get IsSharingEnabled() {
-        return Doc.UserDoc().isSharingEnabled as boolean;
-    }
-    public static set IsSharingEnabled(val) {
-        Doc.UserDoc().isSharingEnabled = val;
-    }
-    public static get defaultAclPrivate() {
-        return Doc.UserDoc().defaultAclPrivate;
-    }
-    public static set defaultAclPrivate(val) {
-        Doc.UserDoc().defaultAclPrivate = val;
-    }
-    public static CurrentUserEmail: string = '';
-    public static get CurrentUserEmailNormalized() {
-        return normalizeEmail(Doc.CurrentUserEmail);
-    }
     public async [HandleUpdate](diff: any) {
         const set = diff.$set;
         const sameAuthor = this.author === Doc.CurrentUserEmail;
@@ -477,17 +334,6 @@ export class Doc extends RefField {
 }
 
 export namespace Doc {
-    // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
-    //     const self = doc[Self];
-    //     return new Promise(res => getField(self, key, ignoreProto, res));
-    // }
-    // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
-    //     return new Promise(async res => {
-    //         const field = await GetAsync(doc, key, ignoreProto);
-    //         return Cast(field, ctor);
-    //     });
-    // }
-
     export function SetContainer(doc: Doc, container: Doc) {
         doc.embedContainer = container;
     }
@@ -621,10 +467,9 @@ export namespace Doc {
     /**
      * @returns the index of doc toFind in list of docs, -1 otherwise
      */
-    export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) {
-        let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1);
-        index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1);
-        return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind));
+    export function IndexOf(toFind: Doc, list: Doc[]) {
+        const index = list.indexOf(toFind);
+        return index !== -1 ? index : list.findIndex(doc => Doc.AreProtosEqual(doc, toFind));
     }
 
     /**
@@ -658,11 +503,10 @@ export namespace Doc {
         }
         const list = Cast(listDoc[key], listSpec(Doc));
         if (list) {
-            if (allowDuplicates !== true) {
-                const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1);
+            if (!allowDuplicates) {
+                const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]);
                 if (pind !== -1) {
                     return true;
-                    //list.splice(pind, 1);  // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection
                 }
             }
             if (first) {
@@ -682,22 +526,6 @@ export namespace Doc {
         return false;
     }
 
-    /**
-     * Computes the bounds of the contents of a set of documents.
-     */
-    export function ComputeContentBounds(docList: Doc[]) {
-        const bounds = docList.reduce(
-            (bounds, doc) => ({
-                x: Math.min(NumCast(doc.x), bounds.x),
-                y: Math.min(NumCast(doc.y), bounds.y),
-                r: Math.max(NumCast(doc.x) + doc[Width](), bounds.r),
-                b: Math.max(NumCast(doc.y) + doc[Height](), bounds.b),
-            }),
-            { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
-        );
-        return bounds;
-    }
-
     export function MakeEmbedding(doc: Doc, id?: string) {
         const embedding = !GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
         const layout = Doc.LayoutField(embedding);
@@ -774,7 +602,7 @@ export namespace Doc {
                 } else if (field instanceof RefField) {
                     assignKey(field);
                 } else if (cfield instanceof ComputedField) {
-                    assignKey(cfield[Copy]()); // ComputedField.MakeFunction(cfield.script.originalScript));
+                    assignKey(cfield[Copy]());
                 } else if (field instanceof ObjectField) {
                     await copyObjectField(field);
                 } else if (field instanceof Promise) {
@@ -808,9 +636,7 @@ export namespace Doc {
                 const docAtKey = DocCast(clone[key]);
                 if (docAtKey && !Doc.IsSystem(docAtKey)) {
                     if (!Array.from(cloneMap.values()).includes(docAtKey)) {
-                        if (cloneMap.has(docAtKey[Id])) {
-                            clone[key] = cloneMap.get(docAtKey[Id]);
-                        } else clone[key] = undefined;
+                        clone[key] = cloneMap.get(docAtKey[Id]);
                     } else {
                         repairClone(docAtKey, cloneMap, visited);
                     }
@@ -987,29 +813,6 @@ export namespace Doc {
         return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc };
     }
 
-    export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc {
-        Object.keys(doc).forEach(key => {
-            const field = ProxyField.WithoutProxy(() => doc[key]);
-            if (key === 'proto' && copyProto) {
-                if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) {
-                    overwrite[key] = Doc.Overwrite(doc.proto, overwrite.proto);
-                }
-            } else {
-                if (field instanceof RefField) {
-                    overwrite[key] = field;
-                } else if (field instanceof ObjectField) {
-                    overwrite[key] = ObjectField.MakeCopy(field);
-                } else if (field instanceof Promise) {
-                    debugger; //This shouldn't happend...
-                } else {
-                    overwrite[key] = field;
-                }
-            }
-        });
-
-        return overwrite;
-    }
-
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
         if (infield instanceof List<any>) {
             infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
@@ -1020,7 +823,7 @@ export namespace Doc {
             references.add(doc);
             return;
         }
-        const excludeLists = ['My Recently Closed', 'My Header Bar', 'My Dashboards'].includes(StrCast(doc.title));
+        const excludeLists = [Doc.MyRecentlyClosed, Doc.MyHeaderBar, Doc.MyDashboards].includes(doc);
         if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return;
         references.add(doc);
         Object.keys(doc).forEach(key => {
@@ -1202,9 +1005,6 @@ export namespace Doc {
         // change it to render the target metadata field instead of what it was rendering before and assign it to the template field layout document.
         Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`);
 
-        // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino)
-        Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc);
-
         return true;
     }
 
@@ -1217,30 +1017,16 @@ export namespace Doc {
         return '/doc/' + (doc ? doc[Id] : '');
     }
 
-    export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
-        const doc2Layout = Doc.Layout(doc2);
-        const doc1Layout = Doc.Layout(doc1);
-        const x2 = NumCast(doc2.x) - clusterDistance;
-        const y2 = NumCast(doc2.y) - clusterDistance;
-        const w2 = NumCast(doc2Layout._width) + clusterDistance;
-        const h2 = NumCast(doc2Layout._height) + clusterDistance;
-        const x = NumCast(doc1.x) - clusterDistance;
-        const y = NumCast(doc1.y) - clusterDistance;
-        const w = NumCast(doc1Layout._width) + clusterDistance;
-        const h = NumCast(doc1Layout._height) + clusterDistance;
-        return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+    export function GetBrushHighlightStatus(doc: Doc) {
+        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.GetBrushStatus(doc);
     }
-
-    export function isBrushedHighlightedDegree(doc: Doc) {
-        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc);
-    }
-    export function isBrushedHighlightedDegreeUnmemoized(doc: Doc) {
-        return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegreeUnmemoized(doc);
-    }
-
     export class DocBrush {
-        BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+        BrushedDoc = new Set<Doc>();
         SearchMatchDoc: ObservableMap<Doc, { searchMatch: number }> = new ObservableMap();
+        brushDoc = action((doc: Doc, unbrush: boolean) => {
+            unbrush ? this.BrushedDoc.delete(doc) : this.BrushedDoc.add(doc);
+            doc[Brushed] = !unbrush;
+        });
     }
     export const brushManager = new DocBrush();
 
@@ -1269,12 +1055,12 @@ export namespace Doc {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
     }
     export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) {
-        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? doc[Width]() : 0));
+        return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
         if (!doc) return 0;
-        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[Height]()) / doc[Width]();
-        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? doc[Height]() : 0);
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * NumCast(doc._height)) / NumCast(doc._width);
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0);
         return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
@@ -1330,63 +1116,29 @@ export namespace Doc {
         brushManager.SearchMatchDoc.clear();
     }
 
-    const isBrushedCache = computedFn(function IsBrushed(doc: Doc) {
-        return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc));
-    });
-    export function IsBrushed(doc: Doc) {
-        return isBrushedCache(doc);
-    }
-
     export enum DocBrushStatus {
         unbrushed = 0,
         protoBrushed = 1,
         selfBrushed = 2,
         highlighted = 3,
     }
-    // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
-    export function IsBrushedDegreeUnmemoized(doc: Doc) {
+    // returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
+    export function GetBrushStatus(doc: Doc) {
         if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
-        const status = brushManager.BrushedDoc.has(doc) ? DocBrushStatus.selfBrushed : brushManager.BrushedDoc.has(Doc.GetProto(doc)) ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
-        return status;
-    }
-    export function IsBrushedDegree(doc: Doc) {
-        return computedFn(function IsBrushDegree(doc: Doc) {
-            return Doc.IsBrushedDegreeUnmemoized(doc);
-        })(doc);
+        return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
     }
-    export function BrushDoc(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        runInAction(() => {
-            brushManager.BrushedDoc.set(doc, true);
-            brushManager.BrushedDoc.set(Doc.GetProto(doc), true);
-        });
+    export function BrushDoc(doc: Doc, unbrush = false) {
+        if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+            brushManager.brushDoc(doc, unbrush);
+            brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+        }
         return doc;
     }
     export function UnBrushDoc(doc: Doc) {
-        if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
-        runInAction(() => {
-            brushManager.BrushedDoc.delete(doc);
-            brushManager.BrushedDoc.delete(Doc.GetProto(doc));
-        });
-        return doc;
+        return BrushDoc(doc, true);
     }
-
-    export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) {
-        const linkAnchor2 = DocCast(linkDoc.link_anchor_2);
-        const linkAnchor1 = DocCast(linkDoc.link_anchor_1);
-        if (linkDoc.link_matchEmbeddings) {
-            return [linkAnchor2, linkAnchor2.annotationOn].includes(anchorDoc) ? '2' : '1';
-        }
-        if (Doc.AreProtosEqual(linkAnchor2, anchorDoc) || Doc.AreProtosEqual(linkAnchor2.annotationOn as Doc, anchorDoc)) return '2';
-        return Doc.AreProtosEqual(linkAnchor1, anchorDoc) || Doc.AreProtosEqual(linkAnchor1.annotationOn as Doc, anchorDoc) ? '1' : '2';
-    }
-
-    export function linkFollowUnhighlight() {
-        clearTimeout(UnhighlightTimer);
-        UnhighlightWatchers.forEach(watcher => watcher());
-        UnhighlightWatchers.length = 0;
-        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
-        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    export function UnBrushAllDocs() {
+        Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
     }
 
     let UnhighlightWatchers: (() => void)[] = [];
@@ -1396,6 +1148,13 @@ export namespace Doc {
             UnhighlightWatchers.push(watcher);
         } else watcher();
     }
+    export function linkFollowUnhighlight() {
+        clearTimeout(UnhighlightTimer);
+        UnhighlightWatchers.forEach(watcher => watcher());
+        UnhighlightWatchers.length = 0;
+        highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
+        document.removeEventListener('pointerdown', linkFollowUnhighlight);
+    }
     export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
         linkFollowUnhighlight();
         (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
@@ -1435,9 +1194,6 @@ export namespace Doc {
             });
         });
     }
-    export function UnBrushAllDocs() {
-        runInAction(() => brushManager.BrushedDoc.clear());
-    }
 
     export function getDocTemplate(doc?: Doc) {
         return !doc
@@ -1453,43 +1209,6 @@ export namespace Doc {
             : undefined;
     }
 
-    export function matchFieldValue(doc: Doc, key: string, value: any): boolean {
-        if (Utils.HasTransparencyFilter(value)) {
-            const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1;
-            return isTransparent(StrCast(doc[key]));
-        }
-        if (key === LinkedTo) {
-            // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
-            const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
-            const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = value?.split('=');
-                if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
-                return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
-            };
-            // prettier-ignore
-            return  (value === Doc.FilterNone && !allLinks.length) || 
-                    (value === Doc.FilterAny && !!allLinks.length) ||
-                    (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || 
-                                           matchLink(value,DocCast(link.link_anchor_2)) ));
-        }
-        if (typeof value === 'string') {
-            value = value.replace(`,${Utils.noRecursionHack}`, '');
-        }
-        const fieldVal = doc[key];
-        // prettier-ignore
-        if ((value === Doc.FilterAny && fieldVal !== undefined) || 
-            (value === Doc.FilterNone && fieldVal === undefined)) {
-            return true;
-        }
-        if (Cast(fieldVal, listSpec('string'), []).length) {
-            const vals = StrListCast(fieldVal);
-            const docs = vals.some(v => (v as any) instanceof Doc);
-            if (docs) return value === Field.toString(fieldVal as Field);
-            return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-        }
-        return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
-    }
-
     export function deiconifyView(doc: Doc) {
         StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc);
     }
@@ -1501,34 +1220,27 @@ export namespace Doc {
         doc.layout_fieldKey = deiconify || 'layout';
     }
     export function setDocRangeFilter(container: Opt<Doc>, key: string, range?: readonly number[], modifiers?: 'remove') {
-        //, modifiers: 'remove' | 'set'
         if (!container) return;
 
         const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []);
 
         for (let i = 0; i < childFiltersByRanges.length; i += 3) {
             if (childFiltersByRanges[i] === key) {
-                console.log('this is key inside childfilters by range ' + key);
                 childFiltersByRanges.splice(i, 3);
-                console.log('this is child filters by range ' + childFiltersByRanges);
                 break;
             }
         }
         if (range !== undefined) {
-            console.log('in doc.ts in set range filter');
             childFiltersByRanges.push(key);
             childFiltersByRanges.push(range[0].toString());
             childFiltersByRanges.push(range[1].toString());
             container._childFiltersByRanges = new List<string>(childFiltersByRanges);
-            console.log('this is child filters by range ' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]);
-            console.log('this is new list ' + container._childFiltersByRange);
         }
 
         if (modifiers) {
             childFiltersByRanges.splice(0, 3);
             container._childFiltersByRanges = new List<string>(childFiltersByRanges);
         }
-        console.log('this is child filters by range END' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]);
     }
 
     export const FilterSep = '::';
@@ -1583,28 +1295,16 @@ export namespace Doc {
                 layoutDoc._freeform_scale = NumCast(layoutDoc._freeform_scale, 1) * contentScale;
                 layoutDoc._nativeWidth = undefined;
                 layoutDoc._nativeHeight = undefined;
-                layoutDoc._layout_forceReflow = undefined;
-                layoutDoc._nativeHeightUnfrozen = undefined;
-                layoutDoc._nativeDimModifiable = undefined;
             } else {
                 layoutDoc._layout_autoHeight = false;
                 if (!Doc.NativeWidth(layoutDoc)) {
                     layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth);
                     layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight);
-                    layoutDoc._layout_forceReflow = true;
-                    layoutDoc._nativeHeightUnfrozen = true;
-                    layoutDoc._nativeDimModifiable = true;
                 }
             }
         });
     }
 
-    export function isDocPinned(doc: Doc) {
-        //add this new doc to props.Document
-        const curPres = Doc.ActivePresentation;
-        return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1;
-    }
-
     export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) {
         const style: { [key: string]: any } = {};
         const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
@@ -1628,7 +1328,7 @@ export namespace Doc {
             if (ptx !== undefined && pty !== undefined && newPoint !== undefined) {
                 const firstx = list.length ? NumCast(list[0].x) + ptx - newPoint[0] : 0;
                 const firsty = list.length ? NumCast(list[0].y) + pty - newPoint[1] : 0;
-                docs.map(doc => {
+                docs.forEach(doc => {
                     doc.x = NumCast(doc.x) - firstx;
                     doc.y = NumCast(doc.y) - firsty;
                 });
@@ -1638,15 +1338,25 @@ export namespace Doc {
     }
 
     // prettier-ignore
-    export function toIcon(doc?: Doc, isOpen?: boolean) {
-        switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) {
+    export function toIcon(doc?: Doc, isOpen?: Opt<boolean>) {
+        if (isOpen) return doc?.isFolder ? 'chevron-down' : 'folder-open';
+        switch (StrCast(doc?.type)) {
             case DocumentType.IMG:  return 'image';
             case DocumentType.COMPARISON: return 'columns';
             case DocumentType.RTF: return 'sticky-note';
             case DocumentType.COL:
-                const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc?.title==='Untitled Collection'? 'object-group': 'chalkboard';
-                const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question';
-                return !doc?.isFolder ? folder : chevron;
+                if (doc?.isFolder) {
+                    switch (doc.type_collection) {
+                        default: return isOpen === false ? 'chevron-right' : 'question';
+                    } // prettier-ignore
+                }
+                switch (doc?.type_collection) {
+                    case CollectionViewType.Freeform :   return 'object-group';
+                    case CollectionViewType.NoteTaking : return 'chalkboard';
+                    case CollectionViewType.Schema :     return 'table-cells';
+                    case CollectionViewType.Docking:     return 'solar-panel';
+                    default:                             return 'folder';
+                } // prettier-ignore
             case DocumentType.WEB: return 'globe-asia';
             case DocumentType.SCREENSHOT: return 'photo-video';
             case DocumentType.WEBCAM: return 'video';
@@ -1663,9 +1373,9 @@ export namespace Doc {
             case DocumentType.DATAVIZ: return 'chart-bar';
             case DocumentType.EQUATION: return 'calculator';
             case DocumentType.SIMULATION: return 'rocket';
-            case DocumentType.CONFIG: return 'question-circle';
-            default: return 'question';
+            case DocumentType.CONFIG: return 'folder-closed';
         }
+        return 'question';
     }
 
     ///
@@ -1871,21 +1581,16 @@ ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
 ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
     return Doc.AreProtosEqual(doc1, doc2);
 });
-ScriptingGlobals.add(function DOC(id: string) {
-    console.log("Can't parse a document id in a script");
-    return 'invalid';
-});
 ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
     return Doc.assignDocToField(doc, field, id);
 });
-ScriptingGlobals.add(function docCast(doc: FieldResult): any {
-    return DocCastAsync(doc);
+ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
+    return Cast(doc, Doc);
 });
 ScriptingGlobals.add(function activePresentationItem() {
     const curPres = Doc.ActivePresentation;
     return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)];
 });
-
 ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
     Doc.setDocFilter(container, key, value, modifiers);
 });
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index df74cc9fe..1fa99249a 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -8,6 +8,8 @@ export const Width = Symbol('DocWidth');
 export const Height = Symbol('DocHeight');
 export const Animation = Symbol('DocAnimation');
 export const Highlight = Symbol('DocHighlight');
+export const DocViews = Symbol('DocViews');
+export const Brushed = Symbol('DocBrushed');
 export const DocData = Symbol('DocData');
 export const DocLayout = Symbol('DocLayout');
 export const DocFields = Symbol('DocFields');
@@ -25,4 +27,4 @@ export const Initializing = Symbol('DocInitializing');
 export const ForceServerWrite = Symbol('DocForceServerWrite');
 export const CachedUpdates = Symbol('DocCachedUpdates');
 
-export const DashVersion = 'v0.6.00';
+export const DashVersion = 'v0.6.50';
diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts
index 5ecf25e08..dfd02dbc0 100644
--- a/src/fields/RichTextUtils.ts
+++ b/src/fields/RichTextUtils.ts
@@ -275,7 +275,7 @@ export namespace RichTextUtils {
             } else {
                 docId = backingDocId;
             }
-            return schema.node('image', { src, agnostic, width, docId, float: null, location: 'add:right' });
+            return schema.node('image', { src, agnostic, width, docId, float: null });
         };
 
         const textNode = (schema: any, run: docs_v1.Schema$TextRun) => {
diff --git a/src/fields/Types.ts b/src/fields/Types.ts
index 69dbe9756..337e8ca21 100644
--- a/src/fields/Types.ts
+++ b/src/fields/Types.ts
@@ -1,6 +1,7 @@
 import { DateField } from './DateField';
 import { Doc, Field, FieldResult, Opt } from './Doc';
 import { List } from './List';
+import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
 import { RichTextField } from './RichTextField';
 import { ScriptField } from './ScriptField';
@@ -72,6 +73,8 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal
             }
         } else if (field instanceof (ctor as any)) {
             return field as ToType<T>;
+        } else if (field instanceof ProxyField && field.value instanceof (ctor as any)) {
+            return field.value as ToType<T>;
         }
     }
     return defaultVal === null ? undefined : defaultVal;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 28db77c65..ca02284da 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -234,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol {
  * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)
  */
 export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) {
-    const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
+    const selfKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
     if (!visited) visited = [] as Doc[];
     if (!target || visited.includes(target) || key === selfKey) return;
     visited.push(target);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 498bec6ed..e24bcd733 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -658,7 +658,7 @@ export class MobileInterface extends React.Component {
     // DocButton for switching into ink mode
     @computed get drawInk() {
         return !this.mainContainer || this._activeDoc._type_collection !== CollectionViewType.Docking ? null : (
-            <div className="docButton" id="inkButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.onSwitchInking}>
+            <div className="docButton" id="inkButton" onClick={this.onSwitchInking}>
                 <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib" />
             </div>
         );
@@ -668,7 +668,7 @@ export class MobileInterface extends React.Component {
     @computed get uploadImageButton() {
         if (this._activeDoc.type === DocumentType.COL && this._activeDoc !== this._homeDoc && this._activeDoc._type_collection !== CollectionViewType.Docking && this._activeDoc.title !== 'WORKSPACES') {
             return (
-                <div className="docButton" id="imageButton" title={Doc.isDocPinned(this._activeDoc) ? 'Pen on' : 'Pen off'} onClick={this.toggleUpload}>
+                <div className="docButton" id="imageButton" onClick={this.toggleUpload}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload" />
                 </div>
             );
@@ -692,13 +692,8 @@ export class MobileInterface extends React.Component {
     @computed get pinToPresentation() {
         // Only making button available if it is an image
         if (!(this._activeDoc.type === 'collection' || this._activeDoc.type === 'presentation')) {
-            const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
             return (
-                <div
-                    className="docButton"
-                    title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
-                    style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }}
-                    onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
+                <div className="docButton" title={'Pin to presentation'} style={{ backgroundColor: 'white', color: 'black' }} onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             );
-- 
cgit v1.2.3-70-g09d2


From 50e770e42bd036d092f3c774921be87045dcd1ef Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 30 Nov 2023 21:01:57 -0500
Subject: final rootDoc refactoring

---
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/util/DragManager.ts                     |   6 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |   4 +-
 src/client/util/SearchUtil.ts                      |   6 +-
 src/client/util/SelectionManager.ts                |   2 +-
 src/client/util/SharingManager.tsx                 |   6 +-
 src/client/views/DashboardView.tsx                 |   2 +-
 src/client/views/DocumentButtonBar.tsx             |  16 ++--
 src/client/views/DocumentDecorations.tsx           | 100 ++++++++++-----------
 src/client/views/FilterPanel.tsx                   |   8 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/InkStrokeProperties.ts            |  16 ++--
 src/client/views/MarqueeAnnotator.tsx              |   6 +-
 src/client/views/PropertiesButtons.tsx             |  20 ++---
 src/client/views/PropertiesDocContextSelector.tsx  |   2 +-
 src/client/views/PropertiesView.tsx                |   8 +-
 src/client/views/StyleProvider.tsx                 |   8 +-
 .../views/collections/CollectionNoteTakingView.tsx |   4 +-
 .../views/collections/CollectionStackingView.tsx   |   8 +-
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |   2 +-
 .../CollectionFreeFormLinkView.tsx                 |  12 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  14 +--
 .../collectionLinear/CollectionLinearView.tsx      |   4 +-
 .../collectionSchema/CollectionSchemaView.tsx      |   4 +-
 src/client/views/global/globalScripts.ts           |  24 ++---
 src/client/views/linking/LinkMenu.tsx              |   2 +-
 src/client/views/linking/LinkMenuGroup.tsx         |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 .../components/Recommendation/Recommendation.tsx   |   2 +-
 src/client/views/nodes/ColorBox.tsx                |   6 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   2 +-
 .../nodes/DataVizBox/components/Histogram.tsx      |  11 ++-
 .../nodes/DataVizBox/components/LineChart.tsx      |   4 +-
 .../views/nodes/DataVizBox/components/PieChart.tsx |  10 +--
 .../views/nodes/DataVizBox/components/TableBox.tsx |  16 ++--
 src/client/views/nodes/DocumentIcon.tsx            |   4 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   8 +-
 .../views/nodes/FontIconBox/ButtonInterface.ts     |  10 +--
 src/client/views/nodes/LinkBox.tsx                 |   8 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   4 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   4 +-
 47 files changed, 200 insertions(+), 201 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7070a47e8..a4eaa71ef 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -110,7 +110,7 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.rootDoc.target).then((target) => target && (target.proto.data = new List([self])))"},
+            { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCastAsync(documentView?.props.docViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
             { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -816,7 +816,7 @@ export class CurrentUserUtils {
     // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field
     // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents
     static setupSharedDocs(doc: Doc, sharingDocumentId: string) {
-        const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.rootDoc);}";
+        const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.Document);}";
         
         const sharedScripts = {  treeView_ChildDoubleClick: dblClkScript,  }
         const sharedDocOpts:DocumentOptions = {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 6e4de252d..162a0a11f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -261,7 +261,7 @@ export namespace DragManager {
 
     // drags a linker button and creates a link on drop
     export function StartLinkDrag(ele: HTMLElement, sourceView: DocumentView, sourceDocGetAnchor: undefined | ((addAsAnnotation: boolean) => Doc), downX: number, downY: number, options?: DragOptions) {
-        StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.(true) ?? sourceView.rootDoc), downX, downY, options);
+        StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.(true) ?? sourceView.Document), downX, downY, options);
     }
 
     // drags a column from a schema view
@@ -611,7 +611,7 @@ export namespace DragManager {
 
 ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
     if (readOnly) {
-        return SelectionManager.Views().some(dv => dv.rootDoc.keepZWhenDragged);
+        return SelectionManager.Views().some(dv => dv.Document.keepZWhenDragged);
     }
-    SelectionManager.Views().map(dv => (dv.rootDoc.keepZWhenDragged = !dv.rootDoc.keepZWhenDragged));
+    SelectionManager.Views().map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
 });
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 1a4c2450e..c45143f43 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -112,7 +112,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
                 sizes.push(file.size);
                 modifiedDates.push(file.lastModified);
             });
-            collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch.map(file =>({file})))));
+            collector.push(...(await Networking.UploadFilesToServer<Upload.ImageInformation>(batch.map(file => ({ file })))));
             runInAction(() => (this.completed += batch.length));
         });
 
@@ -158,7 +158,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             y: NumCast(doc.y) + offset,
         };
         const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
-        if (parent?.rootDoc.type === DocumentType.COL) {
+        if (parent?.Document.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
                 importContainer = Docs.Create.MasonryDocument(docs, options);
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 560d6b30f..e51770c25 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -9,7 +9,7 @@ import { StrCast } from '../../fields/Types';
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
 
-    export function SearchCollection(rootDoc: Opt<Doc>, query: string) {
+    export function SearchCollection(collectionDoc: Opt<Doc>, query: string) {
         const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
         const blockedKeys = [
             'x',
@@ -48,8 +48,8 @@ export namespace SearchUtil {
         query = query.toLowerCase();
 
         const results = new Map<Doc, string[]>();
-        if (rootDoc) {
-            const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]);
+        if (collectionDoc) {
+            const docs = DocListCast(collectionDoc[Doc.LayoutFieldKey(collectionDoc)]);
             const docIDs: String[] = [];
             SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => {
                 const dtype = StrCast(doc.type) as DocumentType;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index f7e6fa2dc..25f158f40 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -81,7 +81,7 @@ export namespace SelectionManager {
         return manager.SelectedSchemaDocument;
     }
     export function Docs(): Doc[] {
-        return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._type_collection !== CollectionViewType.Docking);
+        return manager.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking);
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 8d59426ec..34e294a4a 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -162,7 +162,7 @@ export class SharingManager extends React.Component<{}> {
         const { user, sharingDoc } = recipient;
         const target = targetDoc || this.targetDoc!;
         const acl = `acl-${normalizeEmail(user.email)}`;
-        const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+        const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
             distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
             if (permission !== SharingPermissions.None) {
@@ -180,7 +180,7 @@ export class SharingManager extends React.Component<{}> {
         const target = targetDoc || this.targetDoc!;
         const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
 
-        const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.rootDoc);
+        const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.Document);
         docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
             distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
 
@@ -444,7 +444,7 @@ export class SharingManager extends React.Component<{}> {
         const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
         const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
 
-        let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+        let docs = SelectionManager.Views().length < 2 ? [this.targetDoc] : SelectionManager.Views().map(docView => docView.Document);
 
         if (this.myDocAcls) {
             const newDocs: Doc[] = [];
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 2765e95e6..04fb1fe80 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -429,7 +429,7 @@ export class DashboardView extends React.Component {
             isSystem: true,
             layout_explainer: 'All of the trails that you have created will appear here.',
         };
-        const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' });
+        const myTrails = DocUtils.AssignScripts(Docs.Create.TreeDocument([], reqdOpts), { treeView_ChildDoubleClick: 'openPresentation(documentView.Document)' });
         dashboardDoc.myTrails = new PrefetchProxy(myTrails);
 
         const contextMenuScripts = [reqdBtnScript.onClick];
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index bd82f7782..59d7e75da 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -229,7 +229,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
                             onPointerLeave={action(e => (this.subPin = ''))}
                             onClick={e => {
-                                this.props.views().forEach(dv => click(dv!.rootDoc));
+                                this.props.views().forEach(dv => click(dv!.Document));
                                 e.stopPropagation();
                             }}
                         />
@@ -356,7 +356,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                                 const docs = this.props
                                     .views()
                                     .filter(v => v)
-                                    .map(dv => dv!.rootDoc);
+                                    .map(dv => dv!.Document);
                                 TabDocView.PinDoc(docs, {
                                     pinAudioPlay: true,
                                     pinDocLayout: pinLayoutView,
@@ -379,7 +379,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         const docs = this.props
                             .views()
                             .filter(v => v)
-                            .map(dv => dv!.rootDoc);
+                            .map(dv => dv!.Document);
                         TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) });
                         e.stopPropagation();
                     }}>
@@ -560,9 +560,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @action
     toggleTrail = (e: React.PointerEvent) => {
         const rootView = this.props.views()[0];
-        const rootDoc = rootView?.rootDoc;
-        if (rootDoc) {
-            const anchor = rootView.ComponentView?.getAnchor?.(true) ?? rootDoc;
+        const doc = rootView?.Document;
+        if (doc) {
+            const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
             const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true);
             if (trail !== anchor.presentationTrail) {
                 DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' });
@@ -589,9 +589,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     <div style={{ position: 'absolute', zIndex: 1000 }}>
                         <LinkPopup
                             key="popup"
-                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.rootDoc.onClick))}
+                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.Document.onClick))}
                             linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
-                            linkFrom={() => this.props.views().lastElement()?.rootDoc}
+                            linkFrom={() => this.props.views().lastElement()?.Document}
                         />
                     </div>
                 ) : (
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4ede2e2bb..719055149 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -116,11 +116,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     SelectionManager.Views().forEach(d => {
                         if (titleFieldKey === 'title') {
                             d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-');
-                            if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
-                                Doc.RemFromMyPublished(d.rootDoc);
+                            if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) {
+                                Doc.RemFromMyPublished(d.Document);
                             }
-                            if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
-                                Doc.AddToMyPublished(d.rootDoc);
+                            if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) {
+                                Doc.AddToMyPublished(d.Document);
                             }
                         }
                         //@ts-ignore
@@ -128,20 +128,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
                         if (titleField.toString().startsWith('<this>')) {
                             const title = titleField.toString().replace(/<this>\.?/, '');
-                            const curKey = Doc.LayoutFieldKey(d.rootDoc);
+                            const curKey = Doc.LayoutFieldKey(d.Document);
                             if (curKey !== title) {
                                 if (title) {
                                     if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
-                                        d.rootDoc.layout_fieldKey = `layout_${title}`;
-                                        d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title);
-                                        d.rootDoc[`${title}_nativeWidth`] = d.rootDoc[`${title}_nativeHeight`] = 0;
+                                        d.Document.layout_fieldKey = `layout_${title}`;
+                                        d.Document[`layout_${title}`] = FormattedTextBox.LayoutString(title);
+                                        d.Document[`${title}_nativeWidth`] = d.Document[`${title}_nativeHeight`] = 0;
                                     }
                                 } else {
-                                    d.rootDoc.layout_fieldKey = undefined;
+                                    d.Document.layout_fieldKey = undefined;
                                 }
                             }
                         } else {
-                            Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
+                            Doc.SetInPlace(d.Document, titleFieldKey, titleField, true);
                         }
                     }),
                 'edit title'
@@ -157,7 +157,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     };
 
     onContainerDown = (e: React.PointerEvent) => {
-        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
+        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].Document);
         if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
             setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), emptyFunction, emptyFunction);
             e.stopPropagation();
@@ -165,7 +165,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     };
 
     onTitleDown = (e: React.PointerEvent) => {
-        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].rootDoc);
+        const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views()[0].Document);
         if (effectiveLayoutAcl == AclAdmin || effectiveLayoutAcl == AclEdit || effectiveLayoutAcl == AclAugment) {
             setupMoveUpEvents(
                 this,
@@ -189,12 +189,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @action
     onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
         const dragDocView = SelectionManager.Views()[0];
-        const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.rootDoc);
+        const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document);
         if (effectiveLayoutAcl != AclAdmin && effectiveLayoutAcl != AclEdit && effectiveLayoutAcl != AclAugment) {
             return false;
         }
         const containers = new Set<Doc | undefined>();
-        SelectionManager.Views().forEach(v => containers.add(DocCast(v.rootDoc.embedContainer)));
+        SelectionManager.Views().forEach(v => containers.add(DocCast(v.Document.embedContainer)));
         if (containers.size > 1) return false;
         const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(
@@ -256,7 +256,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     };
 
     onMaximizeDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views().lastElement().rootDoc]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
+        setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views().lastElement().Document]) ?? false, emptyFunction, this.onMaximizeClick, false, false);
         e.stopPropagation();
     };
     onMaximizeClick = (e: any): void => {
@@ -264,19 +264,19 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (selectedDocs.length) {
             if (e.ctrlKey) {
                 // open an embedding in a new tab with Ctrl Key
-                CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].rootDoc), OpenWhereMod.right);
+                CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].Document), OpenWhereMod.right);
             } else if (e.shiftKey) {
                 // open centered in a new workspace with Shift Key
-                const embedding = Doc.MakeEmbedding(selectedDocs[0].rootDoc);
+                const embedding = Doc.MakeEmbedding(selectedDocs[0].Document);
                 embedding.embedContainer = undefined;
                 embedding.x = -NumCast(embedding._width) / 2;
                 embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
                 // open same document in new tab
-                CollectionDockingView.ToggleSplit(selectedDocs[0].rootDoc, OpenWhereMod.right);
+                CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right);
             } else {
-                var openDoc = selectedDocs[0].rootDoc;
+                var openDoc = selectedDocs[0].Document;
                 if (openDoc.layout_fieldKey === 'layout_icon') {
                     openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
                     Doc.deiconifyView(openDoc);
@@ -284,7 +284,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 LightboxView.Instance.SetLightboxDoc(
                     openDoc,
                     undefined,
-                    selectedDocs.slice(1).map(view => view.rootDoc)
+                    selectedDocs.slice(1).map(view => view.Document)
                 );
             }
         }
@@ -346,9 +346,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
         const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
-        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI);
-        seldocview.rootDoc.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
-        seldocview.rootDoc.rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
+        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
+        seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
+        seldocview.Document.rotation_centerY = final.y / NumCast(seldocview.layoutDoc._height);
     };
 
     @action
@@ -361,7 +361,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             (e: PointerEvent, down: number[], delta: number[]) => // return false to keep getting events
                 this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]) as any as boolean, 
             action(e => (this._isRotating = false)), // upEvent
-            action(e => (seldocview.rootDoc.rotation_centerX = seldocview.rootDoc.rotation_centerY = 0))
+            action(e => (seldocview.Document.rotation_centerX = seldocview.Document.rotation_centerY = 0))
         ); // prettier-ignore
     };
 
@@ -375,22 +375,22 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
         const seldocview = SelectionManager.Views()[0];
         SelectionManager.Views().forEach(dv => {
-            const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI;
+            const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
             const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
-            const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2];
+            const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
-            const unrotatedDocPos = { x: NumCast(dv.rootDoc.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.rootDoc.y) + localRotCtrOffset[1] - startRotCtr.y };
-            infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot });
+            const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
+            infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot });
         });
         const infoRot = (angle: number, isAbs = false) => {
             SelectionManager.Views().forEach(
                 action(dv => {
-                    const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.rootDoc)!;
+                    const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.Document)!;
                     const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle);
-                    infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
-                    dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
-                    dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
-                    dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
+                    infos.set(dv.Document, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
+                    dv.Document.x = infos.get(dv.Document)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
+                    dv.Document.y = infos.get(dv.Document)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
+                    dv.Document._rotation = ((isAbs ? 0 : NumCast(dv.Document._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
                 })
             );
         };
@@ -410,7 +410,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return false;
             }, // moveEvent
             action(() => {
-                const oldRotation = NumCast(seldocview.rootDoc._rotation);
+                const oldRotation = NumCast(seldocview.Document._rotation);
                 const diff = oldRotation - Math.round(oldRotation / 45) * 45;
                 if (Math.abs(diff) < 5) {
                     if (selectedInk.length) {
@@ -459,7 +459,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
         const first = SelectionManager.Views()[0];
-        const effectiveAcl = GetEffectiveAcl(first.rootDoc);
+        const effectiveAcl = GetEffectiveAcl(first.Document);
         if (!(effectiveAcl == AclAdmin || effectiveAcl == AclEdit || effectiveAcl == AclAugment)) return false;
         if (!first) return false;
         var fixedAspect = Doc.NativeAspect(first.layoutDoc);
@@ -514,7 +514,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     // resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
     //
     resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => {
-        const doc = docView.rootDoc;
+        const doc = docView.Document;
         if (doc.isGroup) {
             DocListCast(doc.data)
                 .map(member => DocumentManager.Instance.getDocumentView(member, docView)!)
@@ -615,7 +615,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return selected.ComponentView.getTitle();
             }
             if (this._titleControlString.startsWith('=')) {
-                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || '';
+                return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
             }
             if (this._titleControlString.startsWith('#')) {
                 return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
@@ -645,24 +645,24 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         }
 
         // sharing
-        const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.rootDoc) : seldocview.rootDoc);
+        const acl = GetEffectiveAcl(!this._showLayoutAcl ? Doc.GetProto(seldocview.Document) : seldocview.Document);
         const docShareMode = HierarchyMapping.get(acl)!.name;
         const shareMode = StrCast(docShareMode);
         var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
 
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
-        const hideDecorations = SnappingManager.GetIsResizing() || seldocview.props.hideDecorations || seldocview.rootDoc.layout_hideDecorations;
+        const hideDecorations = SnappingManager.GetIsResizing() || seldocview.props.hideDecorations || seldocview.Document.layout_hideDecorations;
         const hideResizers =
-            ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.rootDoc)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.layout_hideResizeHandles || this._isRounding || this._isRotating;
-        const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.layout_hideDecorationTitle || this._isRounding || this._isRotating;
-        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
+            ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
+        const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
+        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
         const hideOpenButton =
             this._showNothing ||
             hideDecorations ||
             seldocview.props.hideOpenButton ||
-            seldocview.rootDoc.layout_hideOpenButton ||
-            SelectionManager.Views().some(docView => docView.rootDoc._dragOnlyWithinContainer || docView.rootDoc.isGroup || docView.rootDoc.layout_hideOpenButton) ||
+            seldocview.Document.layout_hideOpenButton ||
+            SelectionManager.Views().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
             this._isRounding ||
             this._isRotating;
         const hideDeleteButton =
@@ -671,10 +671,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             this._isRounding ||
             this._isRotating ||
             seldocview.props.hideDeleteButton ||
-            seldocview.rootDoc.hideDeleteButton ||
+            seldocview.Document.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
                 const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().dataDoc) : AclEdit;
-                return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin;
+                return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
             <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
@@ -686,13 +686,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
         const bounds = this.ClippedBounds;
         const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
-        const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
+        const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
         const rotation = SelectionManager.Views().length == 1 ? seldocview.screenToLocalTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
-        const borderRadius = numberValue(Cast(seldocview.rootDoc.layout_borderRounding, 'string', null));
-        const docMax = Math.min(NumCast(seldocview.rootDoc.width) / 2, NumCast(seldocview.rootDoc.height) / 2);
+        const borderRadius = numberValue(Cast(seldocview.Document.layout_borderRounding, 'string', null));
+        const docMax = Math.min(NumCast(seldocview.Document.width) / 2, NumCast(seldocview.Document.height) / 2);
         const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2);
         const radiusHandle = (borderRadius / docMax) * maxDist;
         const radiusHandleLocation = Math.min(radiusHandle, maxDist);
@@ -740,7 +740,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 {sharingMenu}
                 {!useLock ? null : (
                     <Tooltip key="lock" title={<div className="dash-tooltip">toggle ability to interact with document</div>} placement="top">
-                        <div className="documentDecorations-lock" style={{ color: seldocview.rootDoc._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
+                        <div className="documentDecorations-lock" style={{ color: seldocview.Document._lockedPosition ? 'red' : undefined }} onPointerDown={this.onLockDown}>
                             <FontAwesomeIcon size="sm" icon="lock" />
                         </div>
                     </Tooltip>
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index cb5c9b085..1deca401d 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -21,7 +21,7 @@ import { List } from '../../fields/List';
 import { emptyFunction } from '../../Utils';
 
 interface filterProps {
-    rootDoc: Doc;
+    Document: Doc;
 }
 
 @observer
@@ -34,7 +34,7 @@ export class FilterPanel extends React.Component<filterProps> {
      * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
      */
     @computed get targetDoc() {
-        return this.props.rootDoc;
+        return this.props.Document;
     }
     @computed get targetDocChildKey() {
         const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc);
@@ -113,7 +113,7 @@ export class FilterPanel extends React.Component<filterProps> {
     // }
 
     gatherFieldValues(childDocs: Doc[], facetKey: string) {
-        const valueSet = new Set<string>(StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
+        const valueSet = new Set<string>(StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
         let rtFields = 0;
         let subDocs = childDocs;
         if (subDocs.length > 0) {
@@ -224,7 +224,7 @@ export class FilterPanel extends React.Component<filterProps> {
     facetValues = (facetHeader: string) => {
         const allCollectionDocs = new Set<Doc>();
         SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
-        const set = new Set<string>([...StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
+        const set = new Set<string>([...StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
         if (facetHeader === 'tags')
             allCollectionDocs.forEach(child =>
                 StrListCast(child[facetHeader])
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 09be95542..508339efc 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -386,7 +386,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
 
     @computed get elements() {
         const selView = GestureOverlay.DownDocView;
-        const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._freeform_scale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1);
+        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
         B.left = B.left - width / 2;
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 62165bc48..9efa7baac 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -49,7 +49,7 @@ export class InkStrokeProperties {
         (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(
             action(inkView => {
                 if (!requireCurrPoint || this._currentPoint !== -1) {
-                    const doc = inkView.rootDoc;
+                    const doc = inkView.Document;
                     if (doc.type === DocumentType.INK && doc.width && doc.height) {
                         const ink = Cast(doc.stroke, InkField)?.inkData;
                         if (ink) {
@@ -86,7 +86,7 @@ export class InkStrokeProperties {
     @action
     addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => {
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
-            const doc = view.rootDoc;
+            const doc = view.Document;
             const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
             const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t);
             const splicepts = [...newsegs.left.points, ...newsegs.right.points];
@@ -159,7 +159,7 @@ export class InkStrokeProperties {
         this.applyFunction(
             inkView,
             (view: DocumentView, ink: InkData) => {
-                const doc = view.rootDoc;
+                const doc = view.Document;
                 const newPoints = ink.slice();
                 const brokenIndices = NumListCast(doc.brokenInkIndices);
                 if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) {
@@ -335,7 +335,7 @@ export class InkStrokeProperties {
      * Handles the movement/scaling of a control point.
      */
     snapControl = (inkView: DocumentView, controlIndex: number) => {
-        const inkDoc = inkView.rootDoc;
+        const inkDoc = inkView.Document;
         const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData;
 
         if (ink) {
@@ -378,9 +378,9 @@ export class InkStrokeProperties {
             .filter(doc => doc.type === DocumentType.INK)
             .forEach(doc => {
                 const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView);
-                const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
+                const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.Document ? this.excludeSelfSnapSegs(ink, controlIndex) : []);
                 if (snapped && snapped.distance < snapData.distance) {
-                    const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
+                    const snappedInkPt = doc === inkView.Document ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space
 
                     if (snappedInkPt) {
                         snapData = { nearestPt: snappedInkPt, distance: snapped.distance };
@@ -397,7 +397,7 @@ export class InkStrokeProperties {
      */
     snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => {
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
-            const doc = view.rootDoc;
+            const doc = view.Document;
             const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []);
             const ind = brokenIndices.findIndex(value => value === controlIndex);
             if (ind !== -1) {
@@ -459,7 +459,7 @@ export class InkStrokeProperties {
     @action
     moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
-            const doc = view.rootDoc;
+            const doc = view.Document;
             const closed = InkingStroke.IsClosed(ink);
             const oldHandlePoint = ink[handleIndex];
             const oppositeHandlePoint = ink[oppositeHandleIndex];
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 8b8838464..b9b7e3f0b 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -61,7 +61,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
         if (savedAnnoMap.size === 0) return undefined;
         const savedAnnos = Array.from(savedAnnoMap.values())[0];
-        const doc = this.props.docView().rootDoc;
+        const doc = this.props.docView().Document;
         const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
         if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) {
             const anno = savedAnnos[0];
@@ -213,7 +213,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
                       dragComplete: e => {
                           if (!e.aborted && e.linkDocument) {
                               Doc.GetProto(e.linkDocument).link_relationship = 'cropped image';
-                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().rootDoc.title;
+                              Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView().Document.title;
                               Doc.GetProto(e.linkDocument).link_displayLine = false;
                           }
                       },
@@ -242,7 +242,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             // configure and show the annotation/link menu if a the drag region is big enough
             // copy the temporary marquee to allow for multiple selections (not currently available though).
             const copy = document.createElement('div');
-            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().rootDoc._freeform_scale, 1);
+            const scale = (this.props.scaling?.() || 1) * NumCast(this.props.docView().Document._freeform_scale, 1);
             ['border', 'opacity', 'top', 'left', 'width', 'height'].forEach(prop => (copy.style[prop as any] = marqueeStyle[prop as any]));
             copy.className = 'marqueeAnnotator-annotationBox';
             copy.style.top = parseInt(marqueeStyle.top.toString().replace('px', '')) / scale + this.props.scrollTop + 'px';
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 84b1bf038..7a8a0bfb9 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -40,7 +40,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @observable public static Instance: PropertiesButtons;
 
     @computed get selectedDoc() {
-        return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc;
+        return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.Document;
     }
     @computed get selectedLayoutDoc() {
         return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.layoutDoc;
@@ -65,7 +65,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 toggleType={ToggleType.BUTTON}
                 onClick={undoable(() => {
                     if (SelectionManager.Views().length > 1) {
-                        SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property));
+                        SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property));
                     } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property);
                 }, property)}
             />
@@ -83,7 +83,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'window-restore',
             onClick => {
                 SelectionManager.Views().forEach(dv => {
-                    const containerDoc = dv.rootDoc;
+                    const containerDoc = dv.Document;
                     //containerDoc.followAllLinks =
                     // containerDoc.noShadow =
                     // containerDoc.layout_disableBrushing =
@@ -92,7 +92,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     containerDoc._isLightbox = !containerDoc._isLightbox;
                     //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
                     const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
-                    //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+                    //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
                     containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false)));
                 });
             }
@@ -106,7 +106,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'Switch between title styles',
             on => (on ? <MdSubtitlesOff /> : <MdSubtitles />), // {currentIcon}, //(on ?  <MdSubtitles/> :)  , //,'text-width', on ?  <MdSubtitles/> : <MdSubtitlesOff/>,
             (dv, doc) => {
-                const tdoc = dv?.rootDoc || doc;
+                const tdoc = dv?.Document || doc;
                 const newtitle = !tdoc._layout_showTitle ? 'title' : tdoc._layout_showTitle === 'title' ? 'title:hover' : '';
                 tdoc._layout_showTitle = newtitle ? newtitle : undefined;
             }
@@ -201,7 +201,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     //         on => 'window-restore',
     //         onClick => {
     //             SelectionManager.Views().forEach(dv => {
-    //                 const containerDoc = dv.rootDoc;
+    //                 const containerDoc = dv.Document;
     //                 //containerDoc.followAllLinks =
     //                 // containerDoc.noShadow =
     //                 // containerDoc.disableDocBrushing =
@@ -210,7 +210,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     //                 containerDoc._isLightbox = !containerDoc._isLightbox;
     //                 //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
     //                 const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
-    //                 //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+    //                 //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
     //                 containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false)));
     //             });
     //         }
@@ -236,7 +236,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             '_layout_showCaption',
             on => `${on ? 'Hide' : 'Show'} caption footer`,
             on => (on ? <MdClosedCaptionDisabled /> : <MdClosedCaption />), //'closed-captioning',
-            (dv, doc) => ((dv?.rootDoc || doc)._layout_showCaption = (dv?.rootDoc || doc)._layout_showCaption === undefined ? 'caption' : undefined)
+            (dv, doc) => ((dv?.Document || doc)._layout_showCaption = (dv?.Document || doc)._layout_showCaption === undefined ? 'caption' : undefined)
         );
     }
 
@@ -247,7 +247,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             '_chromeHidden',
             on => `${on ? 'Show' : 'Hide'} editing UI`,
             on => (on ? <TbEditCircle /> : <TbEditCircleOff />), // 'edit',
-            (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden)
+            (dv, doc) => ((dv?.Document || doc)._chromeHidden = !(dv?.Document || doc)._chromeHidden)
         );
     }
 
@@ -443,7 +443,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     @undoBatch
     editOnClickScript = () => {
-        if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, 'onClick'));
+        if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick'));
         else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick');
     };
 
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index d157e7b1c..196250167 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -21,7 +21,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     @computed get _docs() {
         if (!this.props.DocView) return [];
         const target = this.props.DocView.props.Document;
-        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.rootDoc;
+        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.Document;
         const embeddings = DocListCast(target.proto_embeddings);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 5c7af65ee..cde7eeb08 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -449,7 +449,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
      */
     @computed get sharingTable() {
         // all selected docs
-        const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.rootDoc);
+        const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.Document);
         const target = docs[0];
 
         const showAdmin = GetEffectiveAcl(target) == AclAdmin;
@@ -565,7 +565,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed get editableTitle() {
         const titles = new Set<string>();
-        SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
+        SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.Document.title)));
         const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title);
         return (
             <div>
@@ -622,7 +622,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @action
     setTitle = (value: string | number) => {
         if (SelectionManager.Views().length > 1) {
-            SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true));
+            SelectionManager.Views().map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
         } else if (this.dataDoc) {
             if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true);
             else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true);
@@ -1172,7 +1172,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return (
             <PropertiesSection title="Filters" isOpen={this.openFilters} setIsOpen={bool => (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}>
                 <div className="propertiesView-content filters" style={{ position: 'relative', height: 'auto' }}>
-                    <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
+                    <FilterPanel Document={this.selectedDoc ?? Doc.ActiveDashboard!} />
                 </div>
             </PropertiesSection>
         );
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 4a8a62277..fa2768d79 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -249,7 +249,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.docViewPath().lastElement()?.rootDoc._freeform_useClusters
+                        : props?.docViewPath().lastElement()?.Document._freeform_useClusters
                         ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
@@ -270,7 +270,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
             const lock = () => {
-                if (props?.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform) {
+                if (props?.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
                     return doc?.pointerEvents !== 'none' ? null : (
                         <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                             <FontAwesomeIcon icon='lock' size="lg" />
@@ -315,9 +315,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                             color={SettingsManager.userColor}
                             background={showFilterIcon}
                             items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
-                                    .filter(dv => StrListCast(dv.rootDoc.childFilters).length || StrListCast(dv.rootDoc.childRangeFilters).length)
+                                    .filter(dv => StrListCast(dv.Document.childFilters).length || StrListCast(dv.Document.childRangeFilters).length)
                                     .map(dv => ({
-                                text: StrCast(dv.rootDoc.title),
+                                text: StrCast(dv.Document.title),
                                 val: dv as any,
                                 style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
                             } as IListItemProps)) }
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index edf9f1f2c..01915f2f7 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -407,9 +407,9 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     @action
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         const docView = fieldProps.DocumentView?.();
-        if (docView && (e.ctrlKey || docView.rootDoc._createDocOnCR) && ['Enter'].includes(e.key)) {
+        if (docView && (e.ctrlKey || docView.Document._createDocOnCR) && ['Enter'].includes(e.key)) {
             e.stopPropagation?.();
-            const newDoc = Doc.MakeCopy(docView.rootDoc, true);
+            const newDoc = Doc.MakeCopy(docView.Document, true);
             Doc.GetProto(newDoc).text = undefined;
             FormattedTextBox.SelectOnLoad = newDoc[Id];
             return this.addDocument?.(newDoc);
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 582baa7b5..c1333c90e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -285,11 +285,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             e.stopPropagation?.();
             const below = !e.altKey && e.key !== 'Tab';
             const layout_fieldKey = StrCast(docView.LayoutFieldKey);
-            const newDoc = Doc.MakeCopy(docView.rootDoc, true);
-            const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
+            const newDoc = Doc.MakeCopy(docView.Document, true);
+            const dataField = docView.Document[Doc.LayoutFieldKey(newDoc)];
             newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
-            if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
+            if (layout_fieldKey !== 'layout' && docView.Document[layout_fieldKey] instanceof Doc) {
+                newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
             }
             Doc.GetProto(newDoc).text = undefined;
             FormattedTextBox.SelectOnLoad = newDoc[Id];
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9dcd09b17..43653ed75 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -104,8 +104,8 @@ export function CollectionSubView<X>(moreProps?: X) {
                 // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
                 // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
                 // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
-                const rootDoc = Cast(this.props.Document.rootDocument, Doc, null);
-                rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : [];
+                const rootDocument = Cast(this.props.Document.rootDocument, Doc, null);
+                rawdocs = rootDocument && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDocument)] : [];
             }
 
             const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dc2ee63f5..898b453f4 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -257,7 +257,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             <div
                 className="collectionView"
                 onContextMenu={this.onContextMenu}
-                style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.rootDoc?._type_collection === CollectionViewType.Freeform && this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+                style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform && this.layoutDoc._lockedPosition ? 'none' : undefined }}>
                 {this.renderSubView(this.collectionViewType, props)}
             </div>
         );
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 6f8476f10..a5cf89217 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -378,9 +378,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case undefined:
             case OpenWhere.lightbox:   if (this.layoutDoc?._isLightbox) {
                                             const lightboxView = !doc.annotationOn && DocCast(doc.embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.embedContainer)) : undefined;
-                                            const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)];
+                                            const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)];
                                             if (lightboxView && (!data || data instanceof List)) {
-                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]);
+                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List<Doc>([doc]);
                                                 return true;
                                             }
                                        }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index ee5f9fd95..6cd111ac4 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -215,7 +215,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             this.treeViewOpen = !this.treeViewOpen;
         } else {
             // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns,  (2) has no context field ... otherwise make a new embedding
-            const bestEmbedding = docView.rootDoc.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.rootDoc : Doc.BestEmbedding(docView.rootDoc);
+            const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
             this.props.addDocTab(bestEmbedding, OpenWhere.lightbox);
         }
     };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index aca6df3c9..5e0cf1d23 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -37,11 +37,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         this._anchorDisposer = reaction(
             () => [
                 this.props.A.props.ScreenToLocalTransform(),
-                Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
-                Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
+                Cast(Cast(Cast(this.props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+                Cast(Cast(Cast(this.props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
                 this.props.B.props.ScreenToLocalTransform(),
-                Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
-                Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
+                Cast(Cast(Cast(this.props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+                Cast(Cast(Cast(this.props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
             ],
             action(() => {
                 this._start = Date.now();
@@ -223,8 +223,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
         const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
         const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
         const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
-        const aActive = A.isSelected() || A.rootDoc[Brushed];
-        const bActive = B.isSelected() || B.rootDoc[Brushed];
+        const aActive = A.isSelected() || A.Document[Brushed];
+        const bActive = B.isSelected() || B.Document[Brushed];
 
         const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
         const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 307aa8882..fb29fb0d3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1790,7 +1790,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
+                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.Document._viewTransition, 'string', null))}
                 viewDefDivClick={this.props.viewDefDivClick}>
                 {this.underlayViews}
                 {this.contentViews}
@@ -1931,7 +1931,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
     const browseTransitionTime = 500;
     SelectionManager.DeselectAll();
     dv &&
-        DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
+        DocumentManager.Instance.showDocument(dv.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             if (!focused) {
                 const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
                 let containers = dv.props.docViewPath();
@@ -1960,19 +1960,19 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
 });
 ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
     SelectionManager.Views().forEach(view =>
-        view.props.pinToPres(view.rootDoc, {
-            currentFrame: Cast(view.rootDoc.currentFrame, 'number', null),
+        view.props.pinToPres(view.Document, {
+            currentFrame: Cast(view.Document.currentFrame, 'number', null),
             pinData: {
                 poslayoutview: pinContent,
                 dataview: pinContent,
             },
-            pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()),
+            pinViewport: MarqueeView.CurViewBounds(view.Document, view.props.PanelWidth(), view.props.PanelHeight()),
         })
     );
 });
 ScriptingGlobals.add(function bringToFront() {
-    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document));
 });
 ScriptingGlobals.add(function sendToBack(doc: Doc) {
-    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true));
+    SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.Document, true));
 });
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 96eabd88c..a367e3e73 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -126,8 +126,8 @@ export class CollectionLinearView extends CollectionSubView() {
                     Currently playing:
                     {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => (
                         <>
-                            <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.rootDoc, { willZoomCentered: true })}>
-                                {clip.rootDoc.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '  ' : ',')}
+                            <span className="audio-title" onPointerDown={() => DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}>
+                                {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '  ' : ',')}
                             </span>
                             <FontAwesomeIcon icon={!clip.ComponentView?.IsPlaying?.() ? 'play' : 'pause'} size="lg" onPointerDown={() => clip.ComponentView?.TogglePause?.()} />{' '}
                             <FontAwesomeIcon icon="times" size="lg" onPointerDown={() => clip.ComponentView?.Pause?.()} />{' '}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 22b80e21d..0b292a445 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -416,8 +416,8 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     clearSelection = () => SelectionManager.DeselectAll();
 
-    selectRows = (rootDoc: Doc, lastSelected: Doc) => {
-        const index = this.rowIndex(rootDoc);
+    selectRows = (doc: Doc, lastSelected: Doc) => {
+        const index = this.rowIndex(doc);
         const lastSelectedRow = this.rowIndex(lastSelected);
         const startRow = Math.min(lastSelectedRow, index);
         const endRow = Math.max(lastSelectedRow, index);
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index f2ab4ff4b..0ad76db35 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -41,22 +41,22 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
     } else if (selectedViews.length) {
         if (checkResult) {
             const selView = selectedViews.lastElement();
-            const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
-            const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
-            return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
+            const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+            return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
         selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
-            const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
-            const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
+            const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 const obj: { [key: string]: Opt<string> } = {};
                 obj[fieldKey] = color;
-                CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, obj);
+                CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
             } else {
-                dv.rootDoc['_' + fieldKey] = color;
+                dv.Document['_' + fieldKey] = color;
             }
         });
     } else {
@@ -377,12 +377,12 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
  **/
 ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
     const selected = SelectionManager.Views().lastElement();
-    if (selected?.rootDoc.type === DocumentType.WEB) {
+    if (selected?.Document.type === DocumentType.WEB) {
         if (checkResult) {
-            return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
+            return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href);
         }
         selected.ComponentView?.setData?.(url);
-        //selected.rootDoc.data = new WebField(url);
+        //selected.Document.data = new WebField(url);
     }
 });
 ScriptingGlobals.add(function webForward(checkResult?: boolean) {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 9dc133e28..3082c632d 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -54,7 +54,7 @@ export class LinkMenu extends React.Component<Props> {
     };
 
     render() {
-        const sourceDoc = this.props.docView.rootDoc;
+        const sourceDoc = this.props.docView.Document;
         const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc;
         const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds());
 
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index c1a5a634c..5453ed07b 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -46,14 +46,14 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
         const groupItems = Array.from(set.keys()).map(linkDoc => {
             const sourceDoc =
                 this.props.docView.anchorViewDoc ??
-                (this.props.docView.rootDoc.type === DocumentType.LINK //
+                (this.props.docView.Document.type === DocumentType.LINK //
                     ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
                         ? DocCast(linkDoc.link_anchor_1)
                         : DocCast(linkDoc.link_anchor_2)
                     : this.props.sourceDoc);
             const destDoc = !sourceDoc
                 ? undefined
-                : this.props.docView.rootDoc.type === DocumentType.LINK
+                : this.props.docView.Document.type === DocumentType.LINK
                 ? this.props.docView.props.LayoutTemplateString?.includes('link_anchor_1')
                     ? DocCast(linkDoc.link_anchor_2)
                     : DocCast(linkDoc.link_anchor_1)
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index bf2a4e1a9..611771fff 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -82,7 +82,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             },
             emptyFunction,
             action(() => {
-                const trail = DocCast(this.props.docView.rootDoc.presentationTrail);
+                const trail = DocCast(this.props.docView.Document.presentationTrail);
                 if (trail) {
                     Doc.ActivePresentation = trail;
                     DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
index 96846673b..a085e4a66 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx
@@ -19,7 +19,7 @@ export const Recommendation = (props: IRecommendation) => {
                 if (source == 'Dash' && docId) {
                     const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement();
                     if (docView) {
-                        doc = docView.rootDoc;
+                        doc = docView.Document;
                     }
                 } else if (data) {
                     switch (type) {
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 4c7ea4002..2a20d935b 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -60,7 +60,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
                 <SketchPicker
                     onChange={c => Doc.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
-                    color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
+                    color={StrCast(SelectionManager.Views()?.[0]?.Document?._backgroundColor, ActiveInkColor())}
                     presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
                 />
 
@@ -74,8 +74,8 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                             SetActiveInkWidth(e.target.value);
                             SelectionManager.Views()
-                                .filter(i => StrCast(i.rootDoc.type) === DocumentType.INK)
-                                .map(i => (i.rootDoc.stroke_width = Number(e.target.value)));
+                                .filter(i => StrCast(i.Document.type) === DocumentType.INK)
+                                .map(i => (i.Document.stroke_width = Number(e.target.value)));
                         }}
                     />
                 </div>
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index cc2c44435..a626772e4 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -112,7 +112,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @computed get renderVizView() {
         const scale = this.props.NativeDimScaling?.() || 1;
         const sharedProps = {
-            rootDoc: this.Document,
+            Document: this.Document,
             layoutDoc: this.layoutDoc,
             records: this.records,
             axes: this.axes,
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index e67e2bf31..a1bd316f0 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -10,14 +10,13 @@ import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
-import { LinkManager } from '../../../../util/LinkManager';
 import { undoable } from '../../../../util/UndoManager';
 import { PinProps, PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
 
 export interface HistogramProps {
-    rootDoc: Doc;
+    Document: Doc;
     layoutDoc: Doc;
     axes: string[];
     records: { [key: string]: any }[];
@@ -83,9 +82,9 @@ export class Histogram extends React.Component<HistogramProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.rootDoc.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this.props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -115,7 +114,7 @@ export class Histogram extends React.Component<HistogramProps> {
         const anchor = Docs.Create.ConfigDocument({
             title: 'histogram doc selection' + this._currSelected,
         });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.Document);
         return anchor;
     };
 
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index ad34a0c99..7e0391879 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -64,9 +64,9 @@ export class LineChart extends React.Component<LineChartProps> {
     }
     @computed get parentViz() {
         return DocCast(this.props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
         //     .filter(link => {
-        //         return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz;
+        //         return link.link_anchor_1 == this.props.Document.dataViz_parentViz;
         //     }) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 7303eb184..4dbd67485 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -15,7 +15,7 @@ import './Chart.scss';
 import { Checkbox } from '@material-ui/core';
 
 export interface PieChartProps {
-    rootDoc: Doc;
+    Document: Doc;
     layoutDoc: Doc;
     axes: string[];
     records: { [key: string]: any }[];
@@ -76,9 +76,9 @@ export class PieChart extends React.Component<PieChartProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.rootDoc.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this.props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -101,7 +101,7 @@ export class PieChart extends React.Component<PieChartProps> {
             //
             title: 'piechart doc selection' + this._currSelected,
         });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.Document);
         return anchor;
     };
 
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 147dfb182..9ac06cf3c 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -14,7 +14,7 @@ import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.sc
 import './Chart.scss';
 
 interface TableBoxProps {
-    rootDoc: Doc;
+    Document: Doc;
     layoutDoc: Doc;
     records: { [key: string]: any }[];
     selectAxes: (axes: string[]) => void;
@@ -57,9 +57,9 @@ export class TableBox extends React.Component<TableBoxProps> {
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.rootDoc.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this.props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -119,12 +119,12 @@ export class TableBox extends React.Component<TableBoxProps> {
             e,
             e => {
                 // dragging off a column to create a brushed DataVizBox
-                const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!;
+                const sourceAnchorCreator = () => this.props.docView?.()!.Document!;
                 const targetCreator = (annotationOn: Doc | undefined) => {
-                    const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!);
+                    const embedding = Doc.MakeEmbedding(this.props.docView?.()!.Document!);
                     embedding._dataViz = DataVizView.TABLE;
                     embedding._dataViz_axes = new List<string>([col, col]);
-                    embedding._dataViz_parentViz = this.props.rootDoc;
+                    embedding._dataViz_parentViz = this.props.Document;
                     embedding.annotationOn = annotationOn;
                     embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
                     embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
@@ -138,7 +138,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                                 e.linkDocument.link_displayLine = true;
                                 e.linkDocument.link_matchEmbeddings = true;
                                 e.linkDocument.link_displayArrow = true;
-                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc;
+                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
                                 // e.annoDragData.linkSourceDoc.followLinkZoom = false;
                             }
                         },
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index bccbd66e8..49592d993 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -33,7 +33,7 @@ export class DocumentIcon extends React.Component<{ view: DocumentView; index: n
                     background: SettingsManager.userBackgroundColor,
                     transform: `translate(${(left + right) / 2}px, ${top}px)`,
                 }}>
-                <Tooltip title={<>{this.props.view.rootDoc.title}</>}>
+                <Tooltip title={<>{this.props.view.Document.title}</>}>
                     <p>d{this.props.index}</p>
                 </Tooltip>
             </div>
@@ -59,7 +59,7 @@ export class DocumentIconContainer extends React.Component {
                                 const match = node.text.match(/d([0-9]+)/);
                                 if (match) {
                                     const m = parseInt(match[1]);
-                                    const doc = DocumentIcon.DocViews[m].rootDoc;
+                                    const doc = DocumentIcon.DocViews[m].Document;
                                     usedDocuments.add(m);
                                     return factory.createIdentifier(`idToDoc("${doc[Id]}")`);
                                 }
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 50a7f5d7b..ce3650749 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -69,7 +69,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             this.onLinkButtonMoved,
             emptyFunction,
             action((e, doubleTap) => {
-                doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc);
+                doubleTap && DocumentView.showBackLinks(this.props.View.Document);
             }),
             undefined,
             undefined,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b6e90cbf4..81f1a4f03 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1385,11 +1385,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     };
     public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
         this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
-        if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`;
+        if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`;
         this.ViewTimer && clearTimeout(this.ViewTimer);
         return (this.ViewTimer = setTimeout(() => {
             this.layoutDoc._viewTransition = undefined;
-            this.rootDoc._dataTransition = 'inherit';
+            this.Document._dataTransition = 'inherit';
             afterTrans?.();
         }, timeInMs + 10));
     };
@@ -1427,7 +1427,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     get topMost() {
         return this.props.renderDepth === 0;
     }
-    get rootDoc() {
+    get Document() {
         return this.Document;
     }
     get dataDoc() {
@@ -1440,7 +1440,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView;
     }
     get allLinks() {
-        return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.rootDoc);
+        return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
     }
     get LayoutFieldKey() {
         return this.docView?.LayoutFieldKey || 'layout';
diff --git a/src/client/views/nodes/FontIconBox/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
index 0aa2ac8e1..1c034bfbe 100644
--- a/src/client/views/nodes/FontIconBox/ButtonInterface.ts
+++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts
@@ -1,12 +1,12 @@
-import { Doc } from "../../../../fields/Doc";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { ButtonType } from "./FontIconBox";
+import { Doc } from '../../../../fields/Doc';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { ButtonType } from './FontIconBox';
 
 export interface IButtonProps {
     type: string | ButtonType;
-    rootDoc: Doc;
+    Document: Doc;
     label: any;
     icon: IconProp;
     color: string;
     backgroundColor: string;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 30cd65cb4..743075c89 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -35,8 +35,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
             const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse();
             const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse();
-            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) };
-            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) };
+            const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
+            const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
 
             const pts = [] as number[][];
             pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
@@ -65,8 +65,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform();
                     const a_invXf = a.props.ScreenToLocalTransform().inverse();
                     const b_invXf = b.props.ScreenToLocalTransform().inverse();
-                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };
-                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) };
+                    const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
+                    const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
                     const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
 
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 8d3cd1d96..14a8a387a 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -386,8 +386,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         this.selectedPin = pinDoc;
         this.bingSearchBarContents = pinDoc.map;
 
-        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
-        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
+        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
         Doc.setDocFilter(this.layoutDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
 
         this.recolorPin(this.selectedPin, 'green');
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 130ac40c8..a914084f9 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -147,7 +147,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     createPivotForField = (e: React.MouseEvent) => {
         let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
         if (container) {
-            const embedding = Doc.MakeEmbedding(container.rootDoc);
+            const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
             const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders';
             let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField));
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f3929533d..8a927560d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1511,7 +1511,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = this.Document[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
+        const selectOnLoad = DocCast(this.Document.rootDocument, this.Document)[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
         if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = '';
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 816a89489..791a1bfe7 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -239,7 +239,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight));
             });
         } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
-            SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._text_fontSize) && activeSizes.add(StrCast(dv.rootDoc._text_fontSize)));
+            SelectionManager.Views().forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize)));
         }
         return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) };
     }
@@ -359,7 +359,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
                 this.view.focus();
             }
         } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
-            SelectionManager.Views().forEach(dv => (dv.rootDoc._text_fontSize = fontSize));
+            SelectionManager.Views().forEach(dv => (dv.Document._text_fontSize = fontSize));
         } else Doc.UserDoc().fontSize = fontSize;
         this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     };
-- 
cgit v1.2.3-70-g09d2


From b80d27912cd6d8bc4fe039e52d16582bfbe72c74 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Dec 2023 20:29:53 -0500
Subject: mostly working version with latest libraries

---
 package-lock.json                                  | 593 ++++++-----------
 package.json                                       |  10 +-
 src/Utils.ts                                       |  28 +-
 src/client/DocServer.ts                            |  42 +-
 src/client/documents/Documents.ts                  |   7 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/util/DragManager.ts                     |  34 +-
 src/client/util/GroupManager.tsx                   |   7 +-
 src/client/util/Scripting.ts                       |  31 +-
 src/client/util/SharingManager.tsx                 |   2 +-
 src/client/views/InkTranscription.tsx              | 700 ++++++++++-----------
 src/client/views/InkingStroke.tsx                  |   4 +-
 src/client/views/MainView.tsx                      |   6 +-
 .../views/collections/CollectionCarousel3DView.tsx |   6 +-
 src/client/views/collections/TreeView.tsx          |  39 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   3 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |  10 +-
 src/debug/Test.tsx                                 |  18 +-
 src/fields/Doc.ts                                  |  20 +-
 src/fields/ScriptField.ts                          |   8 +-
 src/mobile/ImageUpload.tsx                         |   4 +-
 src/server/ApiManagers/DeleteManager.ts            |  36 +-
 src/server/ApiManagers/UserManager.ts              |   5 +-
 src/server/DashUploadUtils.ts                      |   2 +-
 src/server/RouteManager.ts                         |   3 +-
 src/server/authentication/AuthenticationManager.ts | 117 ++--
 src/server/authentication/DashUserModel.ts         |   7 +-
 src/server/authentication/Passport.ts              |  35 +-
 src/server/database.ts                             | 131 ++--
 src/server/server_Initialization.ts                |  34 +-
 src/server/websocket.ts                            |  35 +-
 src/typings/index.d.ts                             |   1 -
 32 files changed, 910 insertions(+), 1072 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index a3c9c90d4..04e615015 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -79,7 +79,7 @@
                 "express": "^4.18.2",
                 "express-flash": "0.0.2",
                 "express-session": "^1.17.3",
-                "express-validator": "^7.0.1",
+                "express-validator": "^5.3.1",
                 "expressjs": "^1.0.1",
                 "ffmpeg": "0.0.4",
                 "file-loader": "^6.2.0",
@@ -106,7 +106,6 @@
                 "https": "^1.0.0",
                 "https-browserify": "^1.0.0",
                 "i": "^0.3.7",
-                "iink-js": "^2.0.2",
                 "image-data-uri": "^2.0.1",
                 "image-size": "^1.0.2",
                 "image-size-stream": "^1.1.0",
@@ -195,8 +194,8 @@
                 "sass": "^1.69.5",
                 "serializr": "^3.0.2",
                 "shelljs": "^0.8.5",
-                "socket.io": "^2.5.0",
-                "socket.io-client": "^2.5.0",
+                "socket.io": "^4.7.2",
+                "socket.io-client": "^4.7.2",
                 "solr-node": "^1.2.1",
                 "standard-http-error": "^2.0.1",
                 "stream-browserify": "^3.0.0",
@@ -213,6 +212,7 @@
                 "uuid": "^3.4.0",
                 "valid-url": "^1.0.9",
                 "web-request": "^1.0.7",
+                "webpack": "^5.89.0",
                 "webpack-cli": "^5.1.4",
                 "webpack-dev-middleware": "^6.1.1",
                 "webrtc-adapter": "^8.2.3",
@@ -287,7 +287,6 @@
                 "@types/rimraf": "^3.0.2",
                 "@types/sharp": "^0.31.1",
                 "@types/shelljs": "^0.8.15",
-                "@types/socket.io": "^3.0.1",
                 "@types/socket.io-client": "^1.4.36",
                 "@types/socket.io-parser": "^3.0.0",
                 "@types/typescript": "^2.0.0",
@@ -323,7 +322,6 @@
                 "tslint": "^5.20.1",
                 "tslint-loader": "^3.6.0",
                 "typescript": "^4.7.4",
-                "webpack": "^5.89.0",
                 "webpack-dev-server": "^4.15.1",
                 "webpack-hot-middleware": "^2.25.4"
             }
@@ -2727,8 +2725,7 @@
         "node_modules/@socket.io/component-emitter": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
-            "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==",
-            "dev": true
+            "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
         },
         "node_modules/@szmarczak/http-timer": {
             "version": "5.0.1",
@@ -2919,6 +2916,11 @@
                 "@types/node": "*"
             }
         },
+        "node_modules/@types/cookie": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+            "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
+        },
         "node_modules/@types/cookie-parser": {
             "version": "1.4.6",
             "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz",
@@ -3214,7 +3216,6 @@
             "version": "8.21.0",
             "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz",
             "integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==",
-            "dev": true,
             "dependencies": {
                 "@types/estree": "*",
                 "@types/json-schema": "*"
@@ -3224,7 +3225,6 @@
             "version": "3.7.4",
             "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz",
             "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==",
-            "dev": true,
             "dependencies": {
                 "@types/eslint": "*",
                 "@types/estree": "*"
@@ -4033,16 +4033,6 @@
             "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
             "dev": true
         },
-        "node_modules/@types/socket.io": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz",
-            "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==",
-            "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.",
-            "dev": true,
-            "dependencies": {
-                "socket.io": "*"
-            }
-        },
         "node_modules/@types/socket.io-client": {
             "version": "1.4.36",
             "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.36.tgz",
@@ -4300,7 +4290,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
             "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/helper-numbers": "1.11.6",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@@ -4309,26 +4298,22 @@
         "node_modules/@webassemblyjs/floating-point-hex-parser": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
-            "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
-            "dev": true
+            "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
         },
         "node_modules/@webassemblyjs/helper-api-error": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
-            "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
-            "dev": true
+            "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
         },
         "node_modules/@webassemblyjs/helper-buffer": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
-            "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
-            "dev": true
+            "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA=="
         },
         "node_modules/@webassemblyjs/helper-numbers": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
             "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/floating-point-hex-parser": "1.11.6",
                 "@webassemblyjs/helper-api-error": "1.11.6",
@@ -4338,14 +4323,12 @@
         "node_modules/@webassemblyjs/helper-wasm-bytecode": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
-            "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
-            "dev": true
+            "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
         },
         "node_modules/@webassemblyjs/helper-wasm-section": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
             "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@webassemblyjs/helper-buffer": "1.11.6",
@@ -4357,7 +4340,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
             "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
-            "dev": true,
             "dependencies": {
                 "@xtuc/ieee754": "^1.2.0"
             }
@@ -4366,7 +4348,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
             "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
-            "dev": true,
             "dependencies": {
                 "@xtuc/long": "4.2.2"
             }
@@ -4374,14 +4355,12 @@
         "node_modules/@webassemblyjs/utf8": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
-            "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
-            "dev": true
+            "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
         },
         "node_modules/@webassemblyjs/wasm-edit": {
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
             "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@webassemblyjs/helper-buffer": "1.11.6",
@@ -4397,7 +4376,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
             "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@@ -4410,7 +4388,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
             "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@webassemblyjs/helper-buffer": "1.11.6",
@@ -4422,7 +4399,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
             "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@webassemblyjs/helper-api-error": "1.11.6",
@@ -4436,7 +4412,6 @@
             "version": "1.11.6",
             "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
             "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
-            "dev": true,
             "dependencies": {
                 "@webassemblyjs/ast": "1.11.6",
                 "@xtuc/long": "4.2.2"
@@ -4505,14 +4480,12 @@
         "node_modules/@xtuc/ieee754": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
-            "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
-            "dev": true
+            "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
         },
         "node_modules/@xtuc/long": {
             "version": "4.2.2",
             "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
-            "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
-            "dev": true
+            "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
         },
         "node_modules/abbrev": {
             "version": "1.1.1",
@@ -4557,7 +4530,6 @@
             "version": "1.9.0",
             "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
             "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
-            "dev": true,
             "peerDependencies": {
                 "acorn": "^8"
             }
@@ -4578,11 +4550,6 @@
                 "node": ">=6.0"
             }
         },
-        "node_modules/after": {
-            "version": "0.8.2",
-            "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
-            "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
-        },
         "node_modules/agent-base": {
             "version": "6.0.2",
             "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -5190,11 +5157,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/arraybuffer.slice": {
-            "version": "0.0.7",
-            "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
-            "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
-        },
         "node_modules/arrify": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
@@ -5559,11 +5521,6 @@
                 "node": ">= 10.0.0"
             }
         },
-        "node_modules/backo2": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
-            "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
-        },
         "node_modules/bail": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -5633,14 +5590,6 @@
             "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
             "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg="
         },
-        "node_modules/base64-arraybuffer": {
-            "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
-            "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==",
-            "engines": {
-                "node": ">= 0.6.0"
-            }
-        },
         "node_modules/base64-js": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -5773,11 +5722,6 @@
                 "react": ">= 16.12.0"
             }
         },
-        "node_modules/blob": {
-            "version": "0.0.5",
-            "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
-            "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
-        },
         "node_modules/bluebird": {
             "version": "3.7.2",
             "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -9295,7 +9239,6 @@
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
             "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
-            "dev": true,
             "engines": {
                 "node": ">=6.0"
             }
@@ -9381,16 +9324,6 @@
                 "node": ">= 10"
             }
         },
-        "node_modules/clipboard": {
-            "version": "1.7.1",
-            "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
-            "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
-            "dependencies": {
-                "good-listener": "^1.2.2",
-                "select": "^1.1.2",
-                "tiny-emitter": "^2.0.0"
-            }
-        },
         "node_modules/cliss": {
             "version": "0.0.2",
             "resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz",
@@ -9616,11 +9549,6 @@
                 "node": ">= 12"
             }
         },
-        "node_modules/component-bind": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
-            "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
-        },
         "node_modules/component-emitter": {
             "version": "1.3.1",
             "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -9629,11 +9557,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/component-inherit": {
-            "version": "0.0.3",
-            "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
-            "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
-        },
         "node_modules/compress-commons": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz",
@@ -10221,11 +10144,6 @@
                 "node": "*"
             }
         },
-        "node_modules/crypto-js": {
-            "version": "3.3.0",
-            "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
-            "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
-        },
         "node_modules/css-box-model": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -11215,11 +11133,6 @@
                 "node": ">=0.4.0"
             }
         },
-        "node_modules/delegate": {
-            "version": "3.2.0",
-            "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
-            "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
-        },
         "node_modules/delegates": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -11829,58 +11742,64 @@
             }
         },
         "node_modules/engine.io": {
-            "version": "3.6.1",
-            "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.1.tgz",
-            "integrity": "sha512-dfs8EVg/i7QjFsXxn7cCRQ+Wai1G1TlEvHhdYEi80fxn5R1vZ2K661O6v/rezj1FP234SZ14r9CmJke99iYDGg==",
+            "version": "6.5.4",
+            "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
+            "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==",
             "dependencies": {
+                "@types/cookie": "^0.4.1",
+                "@types/cors": "^2.8.12",
+                "@types/node": ">=10.0.0",
                 "accepts": "~1.3.4",
                 "base64id": "2.0.0",
                 "cookie": "~0.4.1",
-                "debug": "~4.1.0",
-                "engine.io-parser": "~2.2.0",
-                "ws": "~7.4.2"
+                "cors": "~2.8.5",
+                "debug": "~4.3.1",
+                "engine.io-parser": "~5.2.1",
+                "ws": "~8.11.0"
             },
             "engines": {
-                "node": ">=8.0.0"
+                "node": ">=10.2.0"
             }
         },
         "node_modules/engine.io-client": {
-            "version": "3.5.3",
-            "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz",
-            "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==",
-            "dependencies": {
-                "component-emitter": "~1.3.0",
-                "component-inherit": "0.0.3",
-                "debug": "~3.1.0",
-                "engine.io-parser": "~2.2.0",
-                "has-cors": "1.1.0",
-                "indexof": "0.0.1",
-                "parseqs": "0.0.6",
-                "parseuri": "0.0.6",
-                "ws": "~7.4.2",
-                "xmlhttprequest-ssl": "~1.6.2",
-                "yeast": "0.1.2"
+            "version": "6.5.3",
+            "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
+            "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
+            "dependencies": {
+                "@socket.io/component-emitter": "~3.1.0",
+                "debug": "~4.3.1",
+                "engine.io-parser": "~5.2.1",
+                "ws": "~8.11.0",
+                "xmlhttprequest-ssl": "~2.0.0"
             }
         },
         "node_modules/engine.io-client/node_modules/debug": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-            "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
             "dependencies": {
-                "ms": "2.0.0"
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
             }
         },
         "node_modules/engine.io-client/node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
         "node_modules/engine.io-client/node_modules/ws": {
-            "version": "7.4.6",
-            "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
-            "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+            "version": "8.11.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+            "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
             "engines": {
-                "node": ">=8.3.0"
+                "node": ">=10.0.0"
             },
             "peerDependencies": {
                 "bufferutil": "^4.0.1",
@@ -11896,32 +11815,40 @@
             }
         },
         "node_modules/engine.io-parser": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
-            "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
-            "dependencies": {
-                "after": "0.8.2",
-                "arraybuffer.slice": "~0.0.7",
-                "base64-arraybuffer": "0.1.4",
-                "blob": "0.0.5",
-                "has-binary2": "~1.0.2"
+            "version": "5.2.1",
+            "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
+            "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
+            "engines": {
+                "node": ">=10.0.0"
             }
         },
         "node_modules/engine.io/node_modules/debug": {
-            "version": "4.1.1",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-            "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-            "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
             "dependencies": {
-                "ms": "^2.1.1"
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
             }
         },
+        "node_modules/engine.io/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
         "node_modules/engine.io/node_modules/ws": {
-            "version": "7.4.6",
-            "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
-            "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
+            "version": "8.11.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+            "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
             "engines": {
-                "node": ">=8.3.0"
+                "node": ">=10.0.0"
             },
             "peerDependencies": {
                 "bufferutil": "^4.0.1",
@@ -11940,7 +11867,6 @@
             "version": "5.15.0",
             "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
             "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
-            "dev": true,
             "dependencies": {
                 "graceful-fs": "^4.2.4",
                 "tapable": "^2.2.0"
@@ -12091,8 +12017,7 @@
         "node_modules/es-module-lexer": {
             "version": "1.4.1",
             "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
-            "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==",
-            "dev": true
+            "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
         },
         "node_modules/es-set-tostringtag": {
             "version": "2.0.1",
@@ -13416,7 +13341,6 @@
             "version": "5.1.1",
             "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
             "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-            "dev": true,
             "dependencies": {
                 "esrecurse": "^4.3.0",
                 "estraverse": "^4.1.1"
@@ -13778,7 +13702,6 @@
             "version": "4.3.0",
             "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
             "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
-            "dev": true,
             "dependencies": {
                 "estraverse": "^5.2.0"
             },
@@ -13790,7 +13713,6 @@
             "version": "5.3.0",
             "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
             "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-            "dev": true,
             "engines": {
                 "node": ">=4.0"
             }
@@ -13799,7 +13721,6 @@
             "version": "4.3.0",
             "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
             "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-            "dev": true,
             "engines": {
                 "node": ">=4.0"
             }
@@ -14124,15 +14045,15 @@
             ]
         },
         "node_modules/express-validator": {
-            "version": "7.0.1",
-            "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.0.1.tgz",
-            "integrity": "sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==",
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-5.3.1.tgz",
+            "integrity": "sha512-g8xkipBF6VxHbO1+ksC7nxUU7+pWif0+OZXjZTybKJ/V0aTVhuCoHbyhIPgSYVldwQLocGExPtB2pE0DqK4jsw==",
             "dependencies": {
-                "lodash": "^4.17.21",
-                "validator": "^13.9.0"
+                "lodash": "^4.17.10",
+                "validator": "^10.4.0"
             },
             "engines": {
-                "node": ">= 8.0.0"
+                "node": ">= 6.0.0"
             }
         },
         "node_modules/express/node_modules/body-parser": {
@@ -15725,8 +15646,7 @@
         "node_modules/glob-to-regexp": {
             "version": "0.4.1",
             "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
-            "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
-            "dev": true
+            "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
         },
         "node_modules/global": {
             "version": "4.4.0",
@@ -15811,14 +15731,6 @@
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz",
             "integrity": "sha512-sIVQCiRWOymHbVD1Aw/T9/ijbPYAVGBlgGYd1N9MRKfcyBNSpjr87Vg9nSHm+RCT8ELrvK8IJYJV0QRJuVUkCQ=="
         },
-        "node_modules/good-listener": {
-            "version": "1.2.2",
-            "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-            "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
-            "dependencies": {
-                "delegate": "^3.1.2"
-            }
-        },
         "node_modules/google-auth-library": {
             "version": "9.4.1",
             "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.4.1.tgz",
@@ -16067,24 +15979,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/has-binary2": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
-            "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
-            "dependencies": {
-                "isarray": "2.0.1"
-            }
-        },
-        "node_modules/has-binary2/node_modules/isarray": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-            "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
-        },
-        "node_modules/has-cors": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
-            "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
-        },
         "node_modules/has-flag": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -16722,7 +16616,7 @@
         "node_modules/https": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
-            "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q="
+            "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
         },
         "node_modules/https-browserify": {
             "version": "1.0.0",
@@ -16855,27 +16749,6 @@
             "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
             "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
         },
-        "node_modules/iink-js": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/iink-js/-/iink-js-2.0.2.tgz",
-            "integrity": "sha512-8PEHjjrgQqoZ5pmhN0do8npFmMe5KNxO1om6dLYgCUP23qgyGQZ4/69bZ33galIKuqXgYdZg0PW6EUiCM7fcSw==",
-            "dependencies": {
-                "@babel/runtime": "^7.9.2",
-                "clipboard": "^1.7.1",
-                "crypto-js": "^3.3.0",
-                "d3-selection": "^1.4.1",
-                "json-css": "^1.5.6",
-                "lodash.merge": "^4.6.2",
-                "loglevel": "^1.6.8",
-                "perfect-scrollbar": "^1.5.0",
-                "uuid-js": "^0.7.5"
-            }
-        },
-        "node_modules/iink-js/node_modules/d3-selection": {
-            "version": "1.4.2",
-            "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz",
-            "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg=="
-        },
         "node_modules/image-data-uri": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz",
@@ -17012,11 +16885,6 @@
                 "node": ">=0.8.19"
             }
         },
-        "node_modules/indexof": {
-            "version": "0.0.1",
-            "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
-            "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
-        },
         "node_modules/inflight": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -18089,7 +17957,6 @@
             "version": "27.5.1",
             "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
             "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
-            "dev": true,
             "dependencies": {
                 "@types/node": "*",
                 "merge-stream": "^2.0.0",
@@ -18103,7 +17970,6 @@
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
             "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-            "dev": true,
             "engines": {
                 "node": ">=8"
             }
@@ -18112,7 +17978,6 @@
             "version": "8.1.1",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
             "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-            "dev": true,
             "dependencies": {
                 "has-flag": "^4.0.0"
             },
@@ -18393,11 +18258,6 @@
             "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
             "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
         },
-        "node_modules/json-css": {
-            "version": "1.5.6",
-            "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz",
-            "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA="
-        },
         "node_modules/json-parse-even-better-errors": {
             "version": "2.3.1",
             "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -18796,7 +18656,6 @@
             "version": "4.3.0",
             "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
             "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
-            "dev": true,
             "engines": {
                 "node": ">=6.11.5"
             }
@@ -18883,18 +18742,6 @@
                 "node": ">=4"
             }
         },
-        "node_modules/loglevel": {
-            "version": "1.8.1",
-            "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
-            "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
-            "engines": {
-                "node": ">= 0.6.0"
-            },
-            "funding": {
-                "type": "tidelift",
-                "url": "https://tidelift.com/funding/github/npm/loglevel"
-            }
-        },
         "node_modules/longest-streak": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -19491,8 +19338,7 @@
         "node_modules/merge-stream": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
-            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
-            "dev": true
+            "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
         },
         "node_modules/merge2": {
             "version": "1.4.1",
@@ -21261,8 +21107,7 @@
         "node_modules/neo-async": {
             "version": "2.6.2",
             "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
-            "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
-            "dev": true
+            "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
         },
         "node_modules/netmask": {
             "version": "2.0.2",
@@ -24961,16 +24806,6 @@
                 "url": "https://ko-fi.com/killymxi"
             }
         },
-        "node_modules/parseqs": {
-            "version": "0.0.6",
-            "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
-            "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
-        },
-        "node_modules/parseuri": {
-            "version": "0.0.6",
-            "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
-            "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
-        },
         "node_modules/parseurl": {
             "version": "1.3.3",
             "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -25242,11 +25077,6 @@
             "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
             "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
         },
-        "node_modules/perfect-scrollbar": {
-            "version": "1.5.5",
-            "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz",
-            "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g=="
-        },
         "node_modules/performance-now": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -26181,7 +26011,6 @@
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
             "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-            "dev": true,
             "dependencies": {
                 "safe-buffer": "^5.1.0"
             }
@@ -28118,11 +27947,6 @@
                 "url": "https://ko-fi.com/killymxi"
             }
         },
-        "node_modules/select": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-            "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
-        },
         "node_modules/select-hose": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -28200,7 +28024,6 @@
             "version": "6.0.1",
             "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
             "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
-            "dev": true,
             "dependencies": {
                 "randombytes": "^2.1.0"
             }
@@ -28650,74 +28473,89 @@
             "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
         },
         "node_modules/socket.io": {
-            "version": "2.5.0",
-            "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz",
-            "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==",
+            "version": "4.7.2",
+            "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz",
+            "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==",
             "dependencies": {
-                "debug": "~4.1.0",
-                "engine.io": "~3.6.0",
-                "has-binary2": "~1.0.2",
-                "socket.io-adapter": "~1.1.0",
-                "socket.io-client": "2.5.0",
-                "socket.io-parser": "~3.4.0"
+                "accepts": "~1.3.4",
+                "base64id": "~2.0.0",
+                "cors": "~2.8.5",
+                "debug": "~4.3.2",
+                "engine.io": "~6.5.2",
+                "socket.io-adapter": "~2.5.2",
+                "socket.io-parser": "~4.2.4"
+            },
+            "engines": {
+                "node": ">=10.2.0"
             }
         },
         "node_modules/socket.io-adapter": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
-            "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
+            "version": "2.5.2",
+            "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
+            "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
+            "dependencies": {
+                "ws": "~8.11.0"
+            }
+        },
+        "node_modules/socket.io-adapter/node_modules/ws": {
+            "version": "8.11.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+            "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
+            "engines": {
+                "node": ">=10.0.0"
+            },
+            "peerDependencies": {
+                "bufferutil": "^4.0.1",
+                "utf-8-validate": "^5.0.2"
+            },
+            "peerDependenciesMeta": {
+                "bufferutil": {
+                    "optional": true
+                },
+                "utf-8-validate": {
+                    "optional": true
+                }
+            }
         },
         "node_modules/socket.io-client": {
-            "version": "2.5.0",
-            "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz",
-            "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==",
-            "dependencies": {
-                "backo2": "1.0.2",
-                "component-bind": "1.0.0",
-                "component-emitter": "~1.3.0",
-                "debug": "~3.1.0",
-                "engine.io-client": "~3.5.0",
-                "has-binary2": "~1.0.2",
-                "indexof": "0.0.1",
-                "parseqs": "0.0.6",
-                "parseuri": "0.0.6",
-                "socket.io-parser": "~3.3.0",
-                "to-array": "0.1.4"
+            "version": "4.7.2",
+            "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
+            "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
+            "dependencies": {
+                "@socket.io/component-emitter": "~3.1.0",
+                "debug": "~4.3.2",
+                "engine.io-client": "~6.5.2",
+                "socket.io-parser": "~4.2.4"
+            },
+            "engines": {
+                "node": ">=10.0.0"
             }
         },
         "node_modules/socket.io-client/node_modules/debug": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-            "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
             "dependencies": {
-                "ms": "2.0.0"
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
             }
         },
-        "node_modules/socket.io-client/node_modules/isarray": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-            "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
-        },
         "node_modules/socket.io-client/node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        },
-        "node_modules/socket.io-client/node_modules/socket.io-parser": {
-            "version": "3.3.3",
-            "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz",
-            "integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==",
-            "dependencies": {
-                "component-emitter": "~1.3.0",
-                "debug": "~3.1.0",
-                "isarray": "2.0.1"
-            }
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
         "node_modules/socket.io-parser": {
             "version": "4.2.4",
             "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
             "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
-            "dev": true,
             "dependencies": {
                 "@socket.io/component-emitter": "~3.1.0",
                 "debug": "~4.3.1"
@@ -28730,7 +28568,6 @@
             "version": "4.3.4",
             "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
             "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-            "dev": true,
             "dependencies": {
                 "ms": "2.1.2"
             },
@@ -28746,41 +28583,29 @@
         "node_modules/socket.io-parser/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-            "dev": true
-        },
-        "node_modules/socket.io/node_modules/component-emitter": {
-            "version": "1.2.1",
-            "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
-            "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA=="
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
         "node_modules/socket.io/node_modules/debug": {
-            "version": "4.1.1",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-            "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-            "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
-            "dependencies": {
-                "ms": "^2.1.1"
-            }
-        },
-        "node_modules/socket.io/node_modules/isarray": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-            "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
-        },
-        "node_modules/socket.io/node_modules/socket.io-parser": {
-            "version": "3.4.3",
-            "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.3.tgz",
-            "integrity": "sha512-1rE4dZN3kCI/E5wixd393hmbqa78vVpkKmnEJhLeWoS/C5hbFYAbcSfnWoaVH43u9ToUVtzKjguxEZq+1XZfCQ==",
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
             "dependencies": {
-                "component-emitter": "1.2.1",
-                "debug": "~4.1.0",
-                "isarray": "2.0.1"
+                "ms": "2.1.2"
             },
             "engines": {
-                "node": ">=10.0.0"
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
             }
         },
+        "node_modules/socket.io/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
         "node_modules/sockjs": {
             "version": "0.3.24",
             "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@@ -29716,7 +29541,6 @@
             "version": "5.3.9",
             "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
             "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
-            "dev": true,
             "dependencies": {
                 "@jridgewell/trace-mapping": "^0.3.17",
                 "jest-worker": "^27.4.5",
@@ -29783,11 +29607,6 @@
             "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz",
             "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw=="
         },
-        "node_modules/tiny-emitter": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
-            "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
-        },
         "node_modules/tiny-inflate": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
@@ -29827,11 +29646,6 @@
                 "node": ">=0.6.0"
             }
         },
-        "node_modules/to-array": {
-            "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
-            "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
-        },
         "node_modules/to-fast-properties": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -31100,14 +30914,6 @@
                 "uuid": "bin/uuid"
             }
         },
-        "node_modules/uuid-js": {
-            "version": "0.7.5",
-            "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz",
-            "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=",
-            "engines": {
-                "node": ">= 0.4.0"
-            }
-        },
         "node_modules/v8-compile-cache": {
             "version": "2.3.0",
             "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -31126,9 +30932,9 @@
             "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA="
         },
         "node_modules/validator": {
-            "version": "13.11.0",
-            "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
-            "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
+            "version": "10.11.0",
+            "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz",
+            "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==",
             "engines": {
                 "node": ">= 0.10"
             }
@@ -31262,7 +31068,6 @@
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
             "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
-            "dev": true,
             "dependencies": {
                 "glob-to-regexp": "^0.4.1",
                 "graceful-fs": "^4.1.2"
@@ -31314,7 +31119,6 @@
             "version": "5.89.0",
             "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
             "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
-            "dev": true,
             "dependencies": {
                 "@types/eslint-scope": "^3.7.3",
                 "@types/estree": "^1.0.0",
@@ -31487,14 +31291,14 @@
             "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
         },
         "node_modules/webpack-dev-middleware/node_modules/schema-utils": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
-            "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==",
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+            "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
             "dependencies": {
                 "@types/json-schema": "^7.0.9",
-                "ajv": "^8.8.0",
+                "ajv": "^8.9.0",
                 "ajv-formats": "^2.1.1",
-                "ajv-keywords": "^5.0.0"
+                "ajv-keywords": "^5.1.0"
             },
             "engines": {
                 "node": ">= 12.13.0"
@@ -31737,22 +31541,30 @@
             }
         },
         "node_modules/webpack-merge": {
-            "version": "5.8.0",
-            "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz",
-            "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==",
+            "version": "5.10.0",
+            "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+            "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
             "dependencies": {
                 "clone-deep": "^4.0.1",
+                "flat": "^5.0.2",
                 "wildcard": "^2.0.0"
             },
             "engines": {
                 "node": ">=10.0.0"
             }
         },
+        "node_modules/webpack-merge/node_modules/flat": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+            "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+            "bin": {
+                "flat": "cli.js"
+            }
+        },
         "node_modules/webpack-sources": {
             "version": "3.2.3",
             "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
             "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
-            "dev": true,
             "engines": {
                 "node": ">=10.13.0"
             }
@@ -31954,9 +31766,9 @@
             }
         },
         "node_modules/wildcard": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
-            "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw=="
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+            "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="
         },
         "node_modules/with": {
             "version": "7.0.2",
@@ -32268,9 +32080,9 @@
             }
         },
         "node_modules/xmlhttprequest-ssl": {
-            "version": "1.6.3",
-            "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",
-            "integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==",
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
+            "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
             "engines": {
                 "node": ">=0.4.0"
             }
@@ -32394,11 +32206,6 @@
                 "fd-slicer": "~1.1.0"
             }
         },
-        "node_modules/yeast": {
-            "version": "0.1.2",
-            "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
-            "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
-        },
         "node_modules/yn": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
diff --git a/package.json b/package.json
index 97247ad67..dad4f4bf0 100644
--- a/package.json
+++ b/package.json
@@ -86,7 +86,6 @@
         "@types/rimraf": "^3.0.2",
         "@types/sharp": "^0.31.1",
         "@types/shelljs": "^0.8.15",
-        "@types/socket.io": "^3.0.1",
         "@types/socket.io-client": "^1.4.36",
         "@types/socket.io-parser": "^3.0.0",
         "@types/typescript": "^2.0.0",
@@ -122,7 +121,6 @@
         "tslint": "^5.20.1",
         "tslint-loader": "^3.6.0",
         "typescript": "^4.7.4",
-        "webpack": "^5.89.0",
         "webpack-dev-server": "^4.15.1",
         "webpack-hot-middleware": "^2.25.4"
     },
@@ -198,7 +196,7 @@
         "express": "^4.18.2",
         "express-flash": "0.0.2",
         "express-session": "^1.17.3",
-        "express-validator": "^7.0.1",
+        "express-validator": "^5.3.1",
         "expressjs": "^1.0.1",
         "ffmpeg": "0.0.4",
         "file-loader": "^6.2.0",
@@ -225,7 +223,6 @@
         "https": "^1.0.0",
         "https-browserify": "^1.0.0",
         "i": "^0.3.7",
-        "iink-js": "^2.0.2",
         "image-data-uri": "^2.0.1",
         "image-size": "^1.0.2",
         "image-size-stream": "^1.1.0",
@@ -314,8 +311,8 @@
         "sass": "^1.69.5",
         "serializr": "^3.0.2",
         "shelljs": "^0.8.5",
-        "socket.io": "^2.5.0",
-        "socket.io-client": "^2.5.0",
+        "socket.io": "^4.7.2",
+        "socket.io-client": "^4.7.2",
         "solr-node": "^1.2.1",
         "standard-http-error": "^2.0.1",
         "stream-browserify": "^3.0.0",
@@ -332,6 +329,7 @@
         "uuid": "^3.4.0",
         "valid-url": "^1.0.9",
         "web-request": "^1.0.7",
+        "webpack": "^5.89.0",
         "webpack-cli": "^5.1.4",
         "webpack-dev-middleware": "^6.1.1",
         "webrtc-adapter": "^8.2.3",
diff --git a/src/Utils.ts b/src/Utils.ts
index 38a59d524..08ddfa817 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,6 +1,8 @@
 import * as v4 from 'uuid/v4';
 import * as v5 from 'uuid/v5';
 import { ColorResult } from 'react-color';
+//import { Socket } from '../node_modules/socket.io-client';
+import { Socket } from '../node_modules/socket.io/dist/index';
 import * as rp from 'request-promise';
 import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
@@ -54,6 +56,14 @@ export namespace Utils {
         return v5(seed, v5.URL);
     }
 
+    export function GenerateMongoId(id: string): string {
+        return id.length !== 36 ? Utils.GenerateDeterministicGuid(id) : id;
+    }
+
+    export function GuestID() {
+        return '__guest__';
+    }
+
     /**
      * Uploads an image buffer to the server and stores with specified filename.  by default the image
      * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large)
@@ -397,14 +407,14 @@ export namespace Utils {
         };
     }
 
-    export function Emit<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T) {
+    export function Emit<T>(socket: { emit: (msg: string, args: T) => void }, message: Message<T>, args: T) {
         log('Emit', message.Name, args, false);
         socket.emit(message.Message, args);
     }
 
-    export function EmitCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
-    export function EmitCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
-    export function EmitCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
+    export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T): Promise<any>;
+    export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
+    export function EmitCallback<T>(socket: Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
         log('Emit', message.Name, args, false);
         if (fn) {
             socket.emit(message.Message, args, loggingCallback('Receiving', fn, message.Name));
@@ -413,20 +423,20 @@ export namespace Utils {
         }
     }
 
-    export function AddServerHandler<T>(socket: SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) {
+    export function AddServerHandler<T>(socket: { on: (event: string, cb: (args: any) => void) => void }, message: Message<T>, handler: (args: T) => any) {
         socket.on(message.Message, loggingCallback('Incoming', handler, message.Name));
     }
 
-    export function AddServerHandlerCallback<T>(socket: SocketIOClient.Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
+    export function AddServerHandlerCallback<T>(socket: { on: (event: string, cb: (arg: T, fn: (res: any) => any) => void) => void }, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
         socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
             log('S receiving', message.Name, arg, true);
             handler([arg, loggingCallback('S sending', fn, message.Name)]);
         });
     }
-    export type RoomHandler = (socket: SocketIOClient.Socket, room: string) => any;
-    export type UsedSockets = SocketIOClient.Socket;
+    export type RoomHandler = (socket: Socket, room: string) => any;
+    export type UsedSockets = Socket;
     export type RoomMessage = 'create or join' | 'created' | 'joined';
-    export function AddRoomHandler(socket: SocketIOClient.Socket, message: RoomMessage, handler: RoomHandler) {
+    export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
         socket.on(message, (room: any) => handler(socket, room));
     }
 }
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 353e11775..df92fe50e 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -7,6 +7,7 @@ import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols';
 import { ObjectField } from '../fields/ObjectField';
 import { RefField } from '../fields/RefField';
 import { DocCast, StrCast } from '../fields/Types';
+import { Socket } from '../../node_modules/socket.io/dist/index';
 //import MobileInkOverlay from '../mobile/MobileInkOverlay';
 import { emptyFunction, Utils } from '../Utils';
 import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message';
@@ -76,7 +77,7 @@ export namespace DocServer {
             json: true,
         });
     }
-    export let _socket: SocketIOClient.Socket;
+    export let _socket: Socket;
     // this client's distinct GUID created at initialization
     let USER_ID: string;
     // indicates whether or not a document is currently being udpated, and, if so, its id
@@ -189,20 +190,20 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
         Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
 
-        // mobile ink overlay socket events to communicate between mobile view and desktop view
-        _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
-            //   MobileInkOverlay.Instance.drawStroke(content);
-        });
-        _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
-            //GestureOverlay.Instance.enableMobileInkOverlay(content);
-            //    MobileInkOverlay.Instance.initMobileInkOverlay(content);
-        });
-        _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
-            //   MobileInkOverlay.Instance.updatePosition(content);
-        });
-        _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
-            //   MobileInkOverlay.Instance.uploadDocument(content);
-        });
+        // // mobile ink overlay socket events to communicate between mobile view and desktop view
+        // _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => {
+        //     //   MobileInkOverlay.Instance.drawStroke(content);
+        // });
+        // _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => {
+        //     //GestureOverlay.Instance.enableMobileInkOverlay(content);
+        //     //    MobileInkOverlay.Instance.initMobileInkOverlay(content);
+        // });
+        // _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => {
+        //     //   MobileInkOverlay.Instance.updatePosition(content);
+        // });
+        // _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => {
+        //     //   MobileInkOverlay.Instance.uploadDocument(content);
+        // });
     }
 
     function errorFunc(): never {
@@ -470,10 +471,13 @@ export namespace DocServer {
         // to their actual RefField | undefined values. This return value either becomes the input
         // argument to the caller's promise (i.e. GetRefFields(["_id1_", "_id2_", "_id3_"]).then(map => //do something with map...))
         // or it is the direct return result if the promise is awaited (i.e. let fields = await GetRefFields(["_id1_", "_id2_", "_id3_"])).
-        return ids.reduce((map, id) => {
-            map[id] = _cache[id] as any;
-            return map;
-        }, {} as { [id: string]: Opt<RefField> });
+        return ids.reduce(
+            (map, id) => {
+                map[id] = _cache[id] as any;
+                return map;
+            },
+            {} as { [id: string]: Opt<RefField> }
+        );
     };
 
     let _GetRefFields: (ids: string[]) => Promise<{ [id: string]: Opt<RefField> }> = errorFunc;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index df81e12f0..4ec100728 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -30,7 +30,6 @@ import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMu
 import { CollectionView } from '../views/collections/CollectionView';
 import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
-import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
 import { AudioBox, media_state } from '../views/nodes/AudioBox';
 import { ColorBox } from '../views/nodes/ColorBox';
@@ -61,6 +60,8 @@ import { VideoBox } from '../views/nodes/VideoBox';
 import { WebBox } from '../views/nodes/WebBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
+//import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
+const DFLT_IMAGE_NATIVE_DIM = '900px';
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
 class EmptyBox {
@@ -1299,7 +1300,7 @@ export namespace DocUtils {
             // links are not a field value, so handled here.  value is an expression of form ([field=]idToDoc("..."))
             const allLinks = LinkManager.Instance.getAllRelatedLinks(doc);
             const matchLink = (value: string, anchor: Doc) => {
-                const linkedToExp = (value ?? "").split('=');
+                const linkedToExp = (value ?? '').split('=');
                 if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value;
                 return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1];
             };
@@ -1918,7 +1919,7 @@ export namespace DocUtils {
                     overwriteDoc.loadingError = (result as any).message;
                     LoadingBox.removeCurrentlyLoading(overwriteDoc);
                 }
-            } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype??"", result, options, overwriteDoc);
+            } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc);
         });
     }
 
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d3ed4ca7c..8b5f23aa4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -561,7 +561,7 @@ export class CurrentUserUtils {
             toolTip: "Empty recently closed",};
         DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")});
 
-        if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) {
+        if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) {
             recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
         }
         return recentlyClosed;
@@ -946,7 +946,7 @@ export class CurrentUserUtils {
                 runInAction(() => CurrentUserUtils.ServerVersion = result.version);
                 Doc.CurrentUserEmail = result.email;
                 resolvedPorts = result.resolvedPorts as any;
-                DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email);
+                DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
                 if (result.cacheDocumentIds) 
                 {
                     const ids = result.cacheDocumentIds.split(";");
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 162a0a11f..427b1c85f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -7,13 +7,14 @@ import { ScriptField } from '../../fields/ScriptField';
 import { ScriptCast, StrCast } from '../../fields/Types';
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
-import * as globalCssVariables from '../views/global/globalCssVariables.scss';
 import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
 import { DocumentView } from '../views/nodes/DocumentView';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
+// import * as globalCssVariables from '../views/global/globalCssVariables.scss';
+const contextMenuZindex = 100002;
 
 export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
 
@@ -85,7 +86,16 @@ export namespace DragManager {
 
     // event called when the drag operation results in a drop action
     export class DropEvent {
-        constructor(readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean) {}
+        constructor(
+            readonly x: number,
+            readonly y: number,
+            readonly complete: DragCompleteEvent,
+            readonly shiftKey: boolean,
+            readonly altKey: boolean,
+            readonly metaKey: boolean,
+            readonly ctrlKey: boolean,
+            readonly embedKey: boolean
+        ) {}
     }
 
     // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -209,16 +219,14 @@ export namespace DragManager {
                             !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
                                 ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
                                 : docDragData.dropAction === 'embed'
-                                ? Doc.BestEmbedding(d)
-                                : docDragData.dropAction === 'add'
-                                ? d
-                                : docDragData.dropAction === 'proto'
-                                ? Doc.GetProto(d)
-                                : docDragData.dropAction === 'copy'
-                                ? (
-                                      await Doc.MakeClone(d)
-                                  ).clone
-                                : d
+                                  ? Doc.BestEmbedding(d)
+                                  : docDragData.dropAction === 'add'
+                                    ? d
+                                    : docDragData.dropAction === 'proto'
+                                      ? Doc.GetProto(d)
+                                      : docDragData.dropAction === 'copy'
+                                        ? (await Doc.MakeClone(d)).clone
+                                        : d
                         )
                     )
                 ).filter(d => d);
@@ -428,7 +436,7 @@ export namespace DragManager {
                 color: 'black',
                 transition: 'none',
                 borderRadius: getComputedStyle(ele).borderRadius,
-                zIndex: globalCssVariables.contextMenuZindex,
+                zIndex: contextMenuZindex,
                 transformOrigin: '0 0',
                 width,
                 height,
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 8973306bf..c8c93b7d0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -52,7 +52,7 @@ export class GroupManager extends React.Component<{}> {
      * Fetches the list of users stored on the database.
      */
     populateUsers = async () => {
-        if (Doc.UserDoc()[Id] !== '__guest__') {
+        if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
             const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
             const raw = JSON.parse(userList) as User[];
             raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
@@ -258,10 +258,7 @@ export class GroupManager extends React.Component<{}> {
             alert('Please select a unique group name');
             return;
         }
-        this.createGroupDoc(
-            value,
-            this.selectedUsers?.map(user => user.value)
-        );
+        this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
         this.selectedUsers = null;
         this.inputRef.current!.value = '';
         this.buttonColour = '#979797';
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 400b63a1c..b3c694024 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -120,6 +120,35 @@ class ScriptingCompilerHost {
         }
         return undefined;
     }
+    resolveModuleNames(moduleNames: string[], containingFile: string): any {
+        const resolvedModules: ts.ResolvedModule[] = [];
+        for (const moduleName of moduleNames) {
+            // try to use standard resolution
+            let result = ts.resolveModuleName(
+                moduleName,
+                containingFile,
+                {},
+                {
+                    fileExists: (fileName: string) => true as any,
+                    readFile: (fileName: string) => 'true',
+                }
+            );
+            if (result.resolvedModule) {
+                resolvedModules.push(result.resolvedModule);
+            } else {
+                // check fallback locations, for simplicity assume that module at location
+                // should be represented by '.d.ts' file
+                //   for (const location of moduleSearchLocations) {
+                //     const modulePath = path.join(location, moduleName + ".d.ts");
+                //     if (fileExists(modulePath)) {
+                //       resolvedModules.push({ resolvedFileName: modulePath });
+                //     }
+                //   }
+            }
+        }
+        return resolvedModules;
+    }
+
     // getDefaultLibFileName(options: ts.CompilerOptions): string {
     getDefaultLibFileName(options: any): string {
         return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
@@ -247,7 +276,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
     const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
     host.writeFile('file.ts', funcScript);
 
-    if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
+    if (typecheck && false) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
     const program = ts.createProgram(['file.ts'], {}, host);
     const testResult = program.emit();
     const outputText = host.readFile('file.js');
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 34e294a4a..403f4e090 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -133,7 +133,7 @@ export class SharingManager extends React.Component<{}> {
      * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
      */
     populateUsers = async () => {
-        if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') {
+        if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
             this.populating = true;
             const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
             const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index 17136a737..b2ac208ca 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -1,350 +1,350 @@
-import * as iink from 'iink-js';
-import { action, observable } from 'mobx';
-import * as React from 'react';
-import { Doc, DocListCast } from '../../fields/Doc';
-import { InkData, InkField, InkTool } from '../../fields/InkField';
-import { Cast, DateCast, NumCast } from '../../fields/Types';
-import { aggregateBounds } from '../../Utils';
-import { DocumentType } from '../documents/DocumentTypes';
-import { DocumentManager } from '../util/DocumentManager';
-import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { InkingStroke } from './InkingStroke';
-import './InkTranscription.scss';
-
-/**
- * Class component that handles inking in writing mode
- */
-export class InkTranscription extends React.Component {
-    static Instance: InkTranscription;
-
-    @observable _mathRegister: any;
-    @observable _mathRef: any;
-    @observable _textRegister: any;
-    @observable _textRef: any;
-    private lastJiix: any;
-    private currGroup?: Doc;
-
-    constructor(props: Readonly<{}>) {
-        super(props);
-
-        InkTranscription.Instance = this;
-    }
-
-    componentWillUnmount() {
-        this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
-        this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
-    }
-
-    @action
-    setMathRef = (r: any) => {
-        if (!this._mathRegister) {
-            this._mathRegister = r
-                ? iink.register(r, {
-                      recognitionParams: {
-                          type: 'MATH',
-                          protocol: 'WEBSOCKET',
-                          server: {
-                              host: 'cloud.myscript.com',
-                              applicationKey: process.env.IINKJS_APP,
-                              hmacKey: process.env.IINKJS_HMAC,
-                              websocket: {
-                                  pingEnabled: false,
-                                  autoReconnect: true,
-                              },
-                          },
-                          iink: {
-                              math: {
-                                  mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'],
-                              },
-                              export: {
-                                  jiix: {
-                                      strokes: true,
-                                  },
-                              },
-                          },
-                      },
-                  })
-                : null;
-        }
-
-        r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
-
-        return (this._mathRef = r);
-    };
-
-    @action
-    setTextRef = (r: any) => {
-        if (!this._textRegister) {
-            this._textRegister = r
-                ? iink.register(r, {
-                      recognitionParams: {
-                          type: 'TEXT',
-                          protocol: 'WEBSOCKET',
-                          server: {
-                              host: 'cloud.myscript.com',
-                              applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
-                              hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
-                              websocket: {
-                                  pingEnabled: false,
-                                  autoReconnect: true,
-                              },
-                          },
-                          iink: {
-                              text: {
-                                  mimeTypes: ['text/plain'],
-                              },
-                              export: {
-                                  jiix: {
-                                      strokes: true,
-                                  },
-                              },
-                          },
-                      },
-                  })
-                : null;
-        }
-
-        r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
-
-        return (this._textRef = r);
-    };
-
-    /**
-     * Handles processing Dash Doc data for ink transcription.
-     *
-     * @param groupDoc the group which contains the ink strokes we want to transcribe
-     * @param inkDocs the ink docs contained within the selected group
-     * @param math boolean whether to do math transcription or not
-     */
-    transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => {
-        if (!groupDoc) return;
-        const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
-
-        const strokes: InkData[] = [];
-        const times: number[] = [];
-        validInks
-            .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
-            .forEach(i => {
-                const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
-                const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
-                strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
-                times.push(DateCast(i.author_date).getDate().getTime());
-            });
-
-        this.currGroup = groupDoc;
-
-        const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
-        const processGestures = false;
-
-        if (math) {
-            this._mathRef.editor.pointerEvents(pointerData, processGestures);
-        } else {
-            this._textRef.editor.pointerEvents(pointerData, processGestures);
-        }
-    };
-
-    /**
-     * Converts the Dash Ink Data to JSON.
-     *
-     * @param stroke The dash ink data
-     * @param time the time of the stroke
-     * @returns json object representation of ink data
-     */
-    inkJSON = (stroke: InkData, time: number) => {
-        return {
-            pointerType: 'PEN',
-            pointerId: 1,
-            x: stroke.map(point => point.X),
-            y: stroke.map(point => point.Y),
-            t: new Array(stroke.length).fill(time),
-            p: new Array(stroke.length).fill(1.0),
-        };
-    };
-
-    /**
-     * Creates subgroups for each word for the whole text transcription
-     * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs)
-     */
-    subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
-        // iterate through the keys of wordInkDocMap
-        wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
-            const selected = inkDocs.slice();
-            if (!selected) {
-                return;
-            }
-            const ctx = await Cast(selected[0].embedContainer, Doc);
-            if (!ctx) {
-                return;
-            }
-            const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
-
-            if (!docView) return;
-            const marqViewRef = docView._marqueeViewRef.current;
-            if (!marqViewRef) return;
-            this.groupInkDocs(selected, docView, word);
-        });
-    };
-
-    /**
-     * Event listener function for when the 'exported' event is heard.
-     *
-     * @param e the event objects
-     * @param ref the ref to the editor
-     */
-    exportInk = (e: any, ref: any) => {
-        const exports = e.detail.exports;
-        if (exports) {
-            if (exports['application/x-latex']) {
-                const latex = exports['application/x-latex'];
-                if (this.currGroup) {
-                    this.currGroup.text = latex;
-                    this.currGroup.title = latex;
-                }
-
-                ref.editor.clear();
-            } else if (exports['text/plain']) {
-                if (exports['application/vnd.myscript.jiix']) {
-                    this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
-                    // map timestamp to strokes
-                    const timestampWord = new Map<number, string>();
-                    this.lastJiix.words.map((word: any) => {
-                        if (word.items) {
-                            word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => {
-                                const ms = Date.parse(i.timestamp);
-                                timestampWord.set(ms, word.label);
-                            });
-                        }
-                    });
-
-                    const wordInkDocMap = new Map<string, Doc[]>();
-                    if (this.currGroup) {
-                        const docList = DocListCast(this.currGroup.data);
-                        docList.forEach((inkDoc: Doc) => {
-                            // just having the times match up and be a unique value (actual timestamp doesn't matter)
-                            const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000;
-                            const word = timestampWord.get(ms);
-                            if (!word) {
-                                return;
-                            }
-                            const entry = wordInkDocMap.get(word);
-                            if (entry) {
-                                entry.push(inkDoc);
-                                wordInkDocMap.set(word, entry);
-                            } else {
-                                const newEntry = [inkDoc];
-                                wordInkDocMap.set(word, newEntry);
-                            }
-                        });
-                        if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
-                    }
-                }
-                const text = exports['text/plain'];
-
-                if (this.currGroup) {
-                    this.currGroup.transcription = text;
-                    this.currGroup.title = text.split('\n')[0];
-                }
-
-                ref.editor.clear();
-            }
-        }
-    };
-
-    /**
-     * Creates the ink grouping once the user leaves the writing mode.
-     */
-    createInkGroup() {
-        // TODO nda - if document being added to is a inkGrouping then we can just add to that group
-        if (Doc.ActiveTool === InkTool.Write) {
-            CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
-                // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
-                const selected = ffView.unprocessedDocs;
-                const newCollection = this.groupInkDocs(
-                    selected.filter(doc => doc.embedContainer),
-                    ffView
-                );
-                ffView.unprocessedDocs = [];
-
-                InkTranscription.Instance.transcribeInk(newCollection, selected, false);
-            });
-        }
-        CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
-    }
-
-    /**
-     * Creates the groupings for a given list of ink docs on a specific doc view
-     * @param selected: the list of ink docs to create a grouping of
-     * @param docView: the view in which we want the grouping to be created
-     * @param word: optional param if the group we are creating is a word (subgrouping individual words)
-     * @returns a new collection Doc or undefined if the grouping fails
-     */
-    groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
-        const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
-
-        // calculate the necessary bounds from the selected ink docs
-        selected.map(
-            action(d => {
-                const x = NumCast(d.x);
-                const y = NumCast(d.y);
-                const width = NumCast(d._width);
-                const height = NumCast(d._height);
-                bounds.push({ x, y, width, height });
-            })
-        );
-
-        // calculate the aggregated bounds
-        const aggregBounds = aggregateBounds(bounds, 0, 0);
-        const marqViewRef = docView._marqueeViewRef.current;
-
-        // set the vals for bounds in marqueeView
-        if (marqViewRef) {
-            marqViewRef._downX = aggregBounds.x;
-            marqViewRef._downY = aggregBounds.y;
-            marqViewRef._lastX = aggregBounds.r;
-            marqViewRef._lastY = aggregBounds.b;
-        }
-
-        // map through all the selected ink strokes and create the groupings
-        selected.map(
-            action(d => {
-                const dx = NumCast(d.x);
-                const dy = NumCast(d.y);
-                delete d.x;
-                delete d.y;
-                delete d.activeFrame;
-                delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
-                // calculate pos based on bounds
-                if (marqViewRef?.Bounds) {
-                    d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
-                    d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
-                }
-                return d;
-            })
-        );
-        docView.props.removeDocument?.(selected);
-        // Gets a collection based on the selected nodes using a marquee view ref
-        const newCollection = marqViewRef?.getCollection(selected, undefined, true);
-        if (newCollection) {
-            newCollection.width = NumCast(newCollection._width);
-            newCollection.height = NumCast(newCollection._height);
-            // if the grouping we are creating is an individual word
-            if (word) {
-                newCollection.title = word;
-            }
-        }
-
-        // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-        newCollection && docView.props.addDocument?.(newCollection);
-        return newCollection;
-    }
-
-    render() {
-        return (
-            <div className="ink-transcription">
-                <div className="math-editor" ref={this.setMathRef} touch-action="none"></div>
-                <div className="text-editor" ref={this.setTextRef} touch-action="none"></div>
-            </div>
-        );
-    }
-}
+// import * as iink from 'iink-js';
+// import { action, observable } from 'mobx';
+// import * as React from 'react';
+// import { Doc, DocListCast } from '../../fields/Doc';
+// import { InkData, InkField, InkTool } from '../../fields/InkField';
+// import { Cast, DateCast, NumCast } from '../../fields/Types';
+// import { aggregateBounds } from '../../Utils';
+// import { DocumentType } from '../documents/DocumentTypes';
+// import { DocumentManager } from '../util/DocumentManager';
+// import { CollectionFreeFormView } from './collections/collectionFreeForm';
+// import { InkingStroke } from './InkingStroke';
+// import './InkTranscription.scss';
+
+// /**
+//  * Class component that handles inking in writing mode
+//  */
+// export class InkTranscription extends React.Component {
+//     static Instance: InkTranscription;
+
+//     @observable _mathRegister: any;
+//     @observable _mathRef: any;
+//     @observable _textRegister: any;
+//     @observable _textRef: any;
+//     private lastJiix: any;
+//     private currGroup?: Doc;
+
+//     constructor(props: Readonly<{}>) {
+//         super(props);
+
+//         InkTranscription.Instance = this;
+//     }
+
+//     componentWillUnmount() {
+//         this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+//         this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+//     }
+
+//     @action
+//     setMathRef = (r: any) => {
+//         if (!this._mathRegister) {
+//             this._mathRegister = r
+//                 ? iink.register(r, {
+//                       recognitionParams: {
+//                           type: 'MATH',
+//                           protocol: 'WEBSOCKET',
+//                           server: {
+//                               host: 'cloud.myscript.com',
+//                               applicationKey: process.env.IINKJS_APP,
+//                               hmacKey: process.env.IINKJS_HMAC,
+//                               websocket: {
+//                                   pingEnabled: false,
+//                                   autoReconnect: true,
+//                               },
+//                           },
+//                           iink: {
+//                               math: {
+//                                   mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'],
+//                               },
+//                               export: {
+//                                   jiix: {
+//                                       strokes: true,
+//                                   },
+//                               },
+//                           },
+//                       },
+//                   })
+//                 : null;
+//         }
+
+//         r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+
+//         return (this._mathRef = r);
+//     };
+
+//     @action
+//     setTextRef = (r: any) => {
+//         if (!this._textRegister) {
+//             this._textRegister = r
+//                 ? iink.register(r, {
+//                       recognitionParams: {
+//                           type: 'TEXT',
+//                           protocol: 'WEBSOCKET',
+//                           server: {
+//                               host: 'cloud.myscript.com',
+//                               applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f',
+//                               hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1',
+//                               websocket: {
+//                                   pingEnabled: false,
+//                                   autoReconnect: true,
+//                               },
+//                           },
+//                           iink: {
+//                               text: {
+//                                   mimeTypes: ['text/plain'],
+//                               },
+//                               export: {
+//                                   jiix: {
+//                                       strokes: true,
+//                                   },
+//                               },
+//                           },
+//                       },
+//                   })
+//                 : null;
+//         }
+
+//         r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef));
+
+//         return (this._textRef = r);
+//     };
+
+//     /**
+//      * Handles processing Dash Doc data for ink transcription.
+//      *
+//      * @param groupDoc the group which contains the ink strokes we want to transcribe
+//      * @param inkDocs the ink docs contained within the selected group
+//      * @param math boolean whether to do math transcription or not
+//      */
+//     transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => {
+//         if (!groupDoc) return;
+//         const validInks = inkDocs.filter(s => s.type === DocumentType.INK);
+
+//         const strokes: InkData[] = [];
+//         const times: number[] = [];
+//         validInks
+//             .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField))
+//             .forEach(i => {
+//                 const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null);
+//                 const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke;
+//                 strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })));
+//                 times.push(DateCast(i.author_date).getDate().getTime());
+//             });
+
+//         this.currGroup = groupDoc;
+
+//         const pointerData = { events: strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) };
+//         const processGestures = false;
+
+//         if (math) {
+//             this._mathRef.editor.pointerEvents(pointerData, processGestures);
+//         } else {
+//             this._textRef.editor.pointerEvents(pointerData, processGestures);
+//         }
+//     };
+
+//     /**
+//      * Converts the Dash Ink Data to JSON.
+//      *
+//      * @param stroke The dash ink data
+//      * @param time the time of the stroke
+//      * @returns json object representation of ink data
+//      */
+//     inkJSON = (stroke: InkData, time: number) => {
+//         return {
+//             pointerType: 'PEN',
+//             pointerId: 1,
+//             x: stroke.map(point => point.X),
+//             y: stroke.map(point => point.Y),
+//             t: new Array(stroke.length).fill(time),
+//             p: new Array(stroke.length).fill(1.0),
+//         };
+//     };
+
+//     /**
+//      * Creates subgroups for each word for the whole text transcription
+//      * @param wordInkDocMap the mapping of words to ink strokes (Ink Docs)
+//      */
+//     subgroupsTranscriptions = (wordInkDocMap: Map<string, Doc[]>) => {
+//         // iterate through the keys of wordInkDocMap
+//         wordInkDocMap.forEach(async (inkDocs: Doc[], word: string) => {
+//             const selected = inkDocs.slice();
+//             if (!selected) {
+//                 return;
+//             }
+//             const ctx = await Cast(selected[0].embedContainer, Doc);
+//             if (!ctx) {
+//                 return;
+//             }
+//             const docView: CollectionFreeFormView = DocumentManager.Instance.getDocumentView(ctx)?.ComponentView as CollectionFreeFormView;
+
+//             if (!docView) return;
+//             const marqViewRef = docView._marqueeViewRef.current;
+//             if (!marqViewRef) return;
+//             this.groupInkDocs(selected, docView, word);
+//         });
+//     };
+
+//     /**
+//      * Event listener function for when the 'exported' event is heard.
+//      *
+//      * @param e the event objects
+//      * @param ref the ref to the editor
+//      */
+//     exportInk = (e: any, ref: any) => {
+//         const exports = e.detail.exports;
+//         if (exports) {
+//             if (exports['application/x-latex']) {
+//                 const latex = exports['application/x-latex'];
+//                 if (this.currGroup) {
+//                     this.currGroup.text = latex;
+//                     this.currGroup.title = latex;
+//                 }
+
+//                 ref.editor.clear();
+//             } else if (exports['text/plain']) {
+//                 if (exports['application/vnd.myscript.jiix']) {
+//                     this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']);
+//                     // map timestamp to strokes
+//                     const timestampWord = new Map<number, string>();
+//                     this.lastJiix.words.map((word: any) => {
+//                         if (word.items) {
+//                             word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => {
+//                                 const ms = Date.parse(i.timestamp);
+//                                 timestampWord.set(ms, word.label);
+//                             });
+//                         }
+//                     });
+
+//                     const wordInkDocMap = new Map<string, Doc[]>();
+//                     if (this.currGroup) {
+//                         const docList = DocListCast(this.currGroup.data);
+//                         docList.forEach((inkDoc: Doc) => {
+//                             // just having the times match up and be a unique value (actual timestamp doesn't matter)
+//                             const ms = DateCast(inkDoc.author_date).getDate().getTime() + 14400000;
+//                             const word = timestampWord.get(ms);
+//                             if (!word) {
+//                                 return;
+//                             }
+//                             const entry = wordInkDocMap.get(word);
+//                             if (entry) {
+//                                 entry.push(inkDoc);
+//                                 wordInkDocMap.set(word, entry);
+//                             } else {
+//                                 const newEntry = [inkDoc];
+//                                 wordInkDocMap.set(word, newEntry);
+//                             }
+//                         });
+//                         if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap);
+//                     }
+//                 }
+//                 const text = exports['text/plain'];
+
+//                 if (this.currGroup) {
+//                     this.currGroup.transcription = text;
+//                     this.currGroup.title = text.split('\n')[0];
+//                 }
+
+//                 ref.editor.clear();
+//             }
+//         }
+//     };
+
+//     /**
+//      * Creates the ink grouping once the user leaves the writing mode.
+//      */
+//     createInkGroup() {
+//         // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+//         if (Doc.ActiveTool === InkTool.Write) {
+//             CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+//                 // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+//                 const selected = ffView.unprocessedDocs;
+//                 const newCollection = this.groupInkDocs(
+//                     selected.filter(doc => doc.embedContainer),
+//                     ffView
+//                 );
+//                 ffView.unprocessedDocs = [];
+
+//                 InkTranscription.Instance.transcribeInk(newCollection, selected, false);
+//             });
+//         }
+//         CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+//     }
+
+//     /**
+//      * Creates the groupings for a given list of ink docs on a specific doc view
+//      * @param selected: the list of ink docs to create a grouping of
+//      * @param docView: the view in which we want the grouping to be created
+//      * @param word: optional param if the group we are creating is a word (subgrouping individual words)
+//      * @returns a new collection Doc or undefined if the grouping fails
+//      */
+//     groupInkDocs(selected: Doc[], docView: CollectionFreeFormView, word?: string): Doc | undefined {
+//         const bounds: { x: number; y: number; width?: number; height?: number }[] = [];
+
+//         // calculate the necessary bounds from the selected ink docs
+//         selected.map(
+//             action(d => {
+//                 const x = NumCast(d.x);
+//                 const y = NumCast(d.y);
+//                 const width = NumCast(d._width);
+//                 const height = NumCast(d._height);
+//                 bounds.push({ x, y, width, height });
+//             })
+//         );
+
+//         // calculate the aggregated bounds
+//         const aggregBounds = aggregateBounds(bounds, 0, 0);
+//         const marqViewRef = docView._marqueeViewRef.current;
+
+//         // set the vals for bounds in marqueeView
+//         if (marqViewRef) {
+//             marqViewRef._downX = aggregBounds.x;
+//             marqViewRef._downY = aggregBounds.y;
+//             marqViewRef._lastX = aggregBounds.r;
+//             marqViewRef._lastY = aggregBounds.b;
+//         }
+
+//         // map through all the selected ink strokes and create the groupings
+//         selected.map(
+//             action(d => {
+//                 const dx = NumCast(d.x);
+//                 const dy = NumCast(d.y);
+//                 delete d.x;
+//                 delete d.y;
+//                 delete d.activeFrame;
+//                 delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+//                 delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+//                 // calculate pos based on bounds
+//                 if (marqViewRef?.Bounds) {
+//                     d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+//                     d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+//                 }
+//                 return d;
+//             })
+//         );
+//         docView.props.removeDocument?.(selected);
+//         // Gets a collection based on the selected nodes using a marquee view ref
+//         const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+//         if (newCollection) {
+//             newCollection.width = NumCast(newCollection._width);
+//             newCollection.height = NumCast(newCollection._height);
+//             // if the grouping we are creating is an individual word
+//             if (word) {
+//                 newCollection.title = word;
+//             }
+//         }
+
+//         // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+//         newCollection && docView.props.addDocument?.(newCollection);
+//         return newCollection;
+//     }
+
+//     render() {
+//         return (
+//             <div className="ink-transcription">
+//                 <div className="math-editor" ref={this.setMathRef} touch-action="none"></div>
+//                 <div className="text-editor" ref={this.setTextRef} touch-action="none"></div>
+//             </div>
+//         );
+//     }
+// }
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 13df352e3..41a2507f9 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -36,7 +36,6 @@ import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
 import { ViewBoxBaseComponent } from './DocComponent';
-import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
 import { Colors } from './global/globalEnums';
 import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
@@ -47,7 +46,8 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
-import Color = require('color');
+// import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
+const INK_MASK_SIZE = 1000;
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 53182497c..1408e3124 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -43,7 +43,6 @@ import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
-import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
 import { KeyManager } from './GlobalKeyHandler';
 import { InkTranscription } from './InkTranscription';
 import { LightboxView } from './LightboxView';
@@ -71,6 +70,9 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
+// import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
+const LEFT_MENU_WIDTH = '60px';
+const TOPBAR_HEIGHT = '37px';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -1063,7 +1065,7 @@ export class MainView extends React.Component {
                 <MarqueeOptionsMenu />
                 <TimelineMenu />
                 <RichTextMenu />
-                <InkTranscription />
+                {/* <InkTranscription /> */}
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
                 <OverlayView />
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index d6368464a..330cb93e4 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -10,11 +10,13 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
-import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
 import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
-
+// import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
+const CAROUSEL3D_CENTER_SCALE = '1';
+const CAROUSEL3D_SIDE_SCALE = '1';
+const CAROUSEL3D_TOP = '0';
 @observer
 export class CollectionCarousel3DView extends CollectionSubView() {
     @computed get scrollSpeed() {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b5666b917..ac79e4fef 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -24,7 +24,6 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
-import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
 import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -36,6 +35,8 @@ import { CollectionView } from './CollectionView';
 import { TreeSort } from './TreeSort';
 import './TreeView.scss';
 import * as React from 'react';
+// import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
+const TREE_BULLET_WIDTH = '10px';
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
@@ -118,16 +119,16 @@ export class TreeView extends React.Component<TreeViewProps> {
         return this.doc._type_collection === CollectionViewType.Docking
             ? this.fieldKey
             : this.props.treeView.dashboardMode
-            ? this.fieldKey
-            : this.props.treeView.fileSysMode
-            ? this.doc.isFolder
-                ? this.fieldKey
-                : 'data' // file system folders display their contents (data).  used to be they displayed their embeddings but now its a tree structure and not a flat list
-            : this.props.treeView.outlineMode || this.childDocs
-            ? this.fieldKey
-            : Doc.noviceMode
-            ? 'layout'
-            : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
+              ? this.fieldKey
+              : this.props.treeView.fileSysMode
+                ? this.doc.isFolder
+                    ? this.fieldKey
+                    : 'data' // file system folders display their contents (data).  used to be they displayed their embeddings but now its a tree structure and not a flat list
+                : this.props.treeView.outlineMode || this.childDocs
+                  ? this.fieldKey
+                  : Doc.noviceMode
+                    ? 'layout'
+                    : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
     }
 
     @computed get doc() {
@@ -832,14 +833,14 @@ export class TreeView extends React.Component<TreeViewProps> {
             ...(this.doc.isFolder
                 ? folderOp
                 : Doc.IsSystem(this.doc)
-                ? []
-                : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
-                ? [openEmbedding, makeFolder]
-                : this.doc._type_collection === CollectionViewType.Docking
-                ? []
-                : this.props.treeView.Document === Doc.MyRecentlyClosed
-                ? [reopenDoc]
-                : [openEmbedding, focusDoc]),
+                  ? []
+                  : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
+                    ? [openEmbedding, makeFolder]
+                    : this.doc._type_collection === CollectionViewType.Docking
+                      ? []
+                      : this.props.treeView.Document === Doc.MyRecentlyClosed
+                        ? [reopenDoc]
+                        : [openEmbedding, focusDoc]),
         ];
     };
     childContextMenuItems = () => {
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 9ac06cf3c..d422a7536 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -10,8 +10,9 @@ import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
 import { DragManager } from '../../../../util/DragManager';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
-import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
 import './Chart.scss';
+//import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
+const DATA_VIZ_TABLE_ROW_HEIGHT = '30px';
 
 interface TableBoxProps {
     Document: Doc;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d10cbac5e..1b5161e47 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -13,8 +13,8 @@ import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkDocPreview } from './LinkDocPreview';
 import * as React from 'react';
-import globalCssVariables = require('../global/globalCssVariables.scss');
-
+// import globalCssVariables = require('../global/globalCssVariables.scss');
+const MEDIUM_GRAY = 'lightGray';
 @observer
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
@@ -77,8 +77,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_1')
             ? 'link_anchor_1'
             : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_2')
-            ? 'link_anchor_2'
-            : '';
+              ? 'link_anchor_2'
+              : '';
         return (
             <div
                 ref={this._ref}
@@ -97,7 +97,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 onPointerDown={this.onPointerDown}
                 onContextMenu={this.specificContextMenu}
                 style={{
-                    border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined,
+                    border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined,
                     background,
                     left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`,
                     top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`,
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 17d3db8fd..c906dcc03 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,13 +1,11 @@
 import * as React from 'react';
-import * as ReactDOM from 'react-dom';
-import { DocServer } from '../client/DocServer';
-import { Doc } from '../fields/Doc';
-import * as Pdfjs from "pdfjs-dist";
-import "pdfjs-dist/web/pdf_viewer.css";
-import { Utils } from '../Utils';
-const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
-
-const protoId = "protoDoc";
-const delegateId = "delegateDoc";
+import * as ReactDOM from 'react-dom/client';
+console.log('ENTERED');
 class Test extends React.Component {
+    render() {
+        return <div> HELLO WORLD </div>;
+    }
 }
+const root = ReactDOM.createRoot(document.getElementById('root')!);
+
+root.render(<Test />);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 525ff7ec0..d441dafec 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -30,7 +30,7 @@ import { ComputedField, ScriptField } from './ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
 import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
 import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
-import JSZip = require('jszip');
+import * as JSZip from 'jszip';
 export const LinkedTo = '-linkedTo';
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -816,7 +816,7 @@ export namespace Doc {
     }
 
     export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
-        if (infield instanceof List<any>) {
+        if (!(infield instanceof Doc)) {
             infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
             return;
         }
@@ -1201,14 +1201,14 @@ export namespace Doc {
         return !doc
             ? undefined
             : doc.isTemplateDoc
-            ? doc
-            : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
-            ? doc.dragFactory
-            : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
-            ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
-                ? Doc.Layout(doc).proto
-                : Doc.Layout(doc)
-            : undefined;
+              ? doc
+              : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc
+                ? doc.dragFactory
+                : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc
+                  ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc
+                      ? Doc.Layout(doc).proto
+                      : Doc.Layout(doc)
+                  : undefined;
     }
 
     export function deiconifyView(doc: Doc) {
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index cd07a8885..03dc71c48 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -45,12 +45,12 @@ const scriptSchema = createSimpleSchema({
 function finalizeScript(script: ScriptField) {
     const comp = CompileScript(script.script.originalScript, script.script.options);
     if (!comp.compiled) {
-        throw new Error("Couldn't compile loaded script");
+        //    throw new Error("Couldn't compile loaded script");
     }
     if (script.setterscript) {
         const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options);
         if (!compset.compiled) {
-            throw new Error("Couldn't compile setter script");
+            //  throw new Error("Couldn't compile setter script");
         }
         (script as any).setterscript = compset;
     }
@@ -97,7 +97,7 @@ export class ScriptField extends ObjectField {
     constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) {
         super();
 
-        const captured = script?.options.capturedVariables;
+        const captured = script?.options?.capturedVariables;
         if (captured) {
             this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString())));
         }
@@ -151,7 +151,7 @@ export class ComputedField extends ScriptField {
     _lastComputedResult: any;
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
     value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
-    _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
+    _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
 
     [ToValue](doc: Doc) {
         return ComputedField.toValue(doc, this);
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index fd119e0bf..ee212e4af 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -5,7 +5,6 @@ import * as rp from 'request-promise';
 import { DocServer } from '../client/DocServer';
 import { Docs } from '../client/documents/Documents';
 import { Networking } from '../client/Network';
-import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/global/globalCssVariables.scss';
 import { MainViewModal } from '../client/views/MainViewModal';
 import { Doc, Opt } from '../fields/Doc';
 import { List } from '../fields/List';
@@ -15,7 +14,8 @@ import { Utils } from '../Utils';
 import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
 import * as React from 'react';
-
+//import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/global/globalCssVariables.scss';
+const DFLT_IMAGE_NATIVE_DIM = '50px';
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
 }
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 56d8aff60..c6c4ca464 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,21 +1,19 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
-import { WebSocket } from "../websocket";
-import { Database } from "../database";
-import { rimraf } from "rimraf";
-import { filesDirectory } from "..";
-import { DashUploadUtils } from "../DashUploadUtils";
-import { mkdirSync } from "fs";
-import RouteSubscriber from "../RouteSubscriber";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _permission_denied } from '../RouteManager';
+import { WebSocket } from '../websocket';
+import { Database } from '../database';
+import { rimraf } from 'rimraf';
+import { filesDirectory } from '..';
+import { DashUploadUtils } from '../DashUploadUtils';
+import { mkdirSync } from 'fs';
+import RouteSubscriber from '../RouteSubscriber';
 
 export default class DeleteManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         register({
             method: Method.GET,
             requireAdminInRelease: true,
-            subscription: new RouteSubscriber("delete").add("target?"),
+            subscription: new RouteSubscriber('delete').add('target?'),
             secureHandler: async ({ req, res }) => {
                 const { target } = req.params;
 
@@ -24,12 +22,12 @@ export default class DeleteManager extends ApiManager {
                 } else {
                     let all = false;
                     switch (target) {
-                        case "all":
+                        case 'all':
                             all = true;
-                        case "database":
+                        case 'database':
                             await WebSocket.doDelete(false);
                             if (!all) break;
-                        case "files":
+                        case 'files':
                             rimraf.sync(filesDirectory);
                             mkdirSync(filesDirectory);
                             await DashUploadUtils.buildFileDirectories();
@@ -39,10 +37,8 @@ export default class DeleteManager extends ApiManager {
                     }
                 }
 
-                res.redirect("/home");
-            }
+                res.redirect('/home');
+            },
         });
-
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 9252202b0..0d36ee957 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -7,6 +7,7 @@ import { Opt } from '../../fields/Doc';
 import { WebSocket } from '../websocket';
 import { resolvedPorts } from '../server_Initialization';
 import { DashVersion } from '../../fields/DocSymbols';
+import { Utils } from '../../Utils';
 
 export const timeMap: { [id: string]: number } = {};
 interface ActivityUnit {
@@ -49,7 +50,7 @@ export default class UserManager extends ApiManager {
             method: Method.GET,
             subscription: '/getUserDocumentIds',
             secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }),
-            publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }),
+            publicHandler: ({ res }) => res.send({ userDocumentId: Utils.GuestID(), linkDatabaseId: 3, sharingDocumentId: 2 }),
         });
 
         register({
@@ -81,7 +82,7 @@ export default class UserManager extends ApiManager {
                         resolvedPorts,
                     })
                 ),
-            publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })),
+            publicHandler: ({ res }) => res.send(JSON.stringify({ userDocumentId: Utils.GuestID(), email: 'guest', resolvedPorts })),
         });
 
         register({
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 643626ae9..2bea15915 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -200,7 +200,7 @@ export namespace DashUploadUtils {
 
         const category = types[0];
         let format = `.${types[1]}`;
-        console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`));
+        console.log(green(`Processing upload of file (${originalFilename}) and format (${format}) with upload type (${type}) in category (${category}).`));
 
         switch (category) {
             case 'image':
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index 5683cd539..540bca776 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -1,6 +1,7 @@
 import { cyan, green, red } from 'colors';
 import { Express, Request, Response } from 'express';
 import { AdminPriviliges } from '.';
+import { Utils } from '../Utils';
 import { DashUserModel } from './authentication/DashUserModel';
 import RouteSubscriber from './RouteSubscriber';
 
@@ -102,7 +103,7 @@ export default class RouteManager {
             let user = req.user as Partial<DashUserModel> | undefined;
             const { originalUrl: target } = req;
             if (process.env.DB === 'MEM' && !user) {
-                user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' };
+                user = { id: 'guest', email: 'guest', userDocumentId: Utils.GuestID() };
             }
             const core = { req, res, isRelease };
             const tryExecute = async (toExecute: (args: any) => any | Promise<any>, args: any) => {
diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts
index 74d8d2523..5bc6e96b4 100644
--- a/src/server/authentication/AuthenticationManager.ts
+++ b/src/server/authentication/AuthenticationManager.ts
@@ -46,7 +46,7 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
     const model = {
         email,
         password,
-        userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(),
+        userDocumentId: email === 'guest' ? Utils.GuestID() : Utils.GenerateGuid(),
         sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(),
         linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(),
         cacheDocumentIds: '',
@@ -54,25 +54,21 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => {
 
     const user = new User(model);
 
-    User.findOne({ email }, (err: any, existingUser: any) => {
-        if (err) {
-            return next(err);
-        }
-        if (existingUser) {
-            return res.redirect('/login');
-        }
-        user.save().then(undefined, (err: any) => {
-            if (err) {
-                return next(err);
+    User.findOne({ email })
+        .then(existingUser => {
+            if (existingUser) {
+                return res.redirect('/login');
             }
-            req.logIn(user, err => {
-                if (err) {
-                    return next(err);
-                }
-                tryRedirectToTarget(req, res);
-            });
-        });
-    });
+            user.save()
+                .then(() => {
+                    req.logIn(user, err => {
+                        if (err) return next(err);
+                        tryRedirectToTarget(req, res);
+                    });
+                })
+                .catch(err => next(err));
+        })
+        .catch(err => next(err));
 };
 
 const tryRedirectToTarget = (req: Request, res: Response) => {
@@ -106,7 +102,9 @@ export let getLogin = (req: Request, res: Response) => {
  */
 export let postLogin = (req: Request, res: Response, next: NextFunction) => {
     if (req.body.email === '') {
-        User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest());
+        User.findOne({ email: 'guest' })
+            .then(user => !user && initializeGuest())
+            .catch(err => err);
         req.body.email = 'guest';
         req.body.password = 'guest';
     } else {
@@ -146,14 +144,7 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => {
  */
 export let getLogout = (req: Request, res: Response) => {
     req.logout(emptyFunction);
-    const sess = req.session;
-    if (sess) {
-        sess.destroy(err => {
-            if (err) {
-                console.log(err);
-            }
-        });
-    }
+    req.session?.destroy(err => err && console.log(err));
     res.redirect('/login');
 };
 
@@ -178,7 +169,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
                 });
             },
             function (token: string, done: any) {
-                User.findOne({ email }, function (err: any, user: DashUserModel) {
+                User.findOne({ email }).then(user => {
                     if (!user) {
                         // NO ACCOUNT WITH SUBMITTED EMAIL
                         res.redirect('/forgotPassword');
@@ -186,9 +177,7 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
                     }
                     user.passwordResetToken = token;
                     user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
-                    user.save().then(undefined, (err: any) => {
-                        done(null, token, user);
-                    });
+                    user.save().then(() => done(null, token, user));
                 });
             },
             function (token: Uint16Array, user: DashUserModel, done: any) {
@@ -227,50 +216,43 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio
 };
 
 export let getReset = function (req: Request, res: Response) {
-    User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) {
-        if (!user || err) {
-            return res.redirect('/forgotPassword');
-        }
-        res.render('reset.pug', {
-            title: 'Reset Password',
-            user: req.user,
-        });
-    });
+    User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } })
+        .then(user => {
+            if (!user) return res.redirect('/forgotPassword');
+            res.render('reset.pug', {
+                title: 'Reset Password',
+                user: req.user,
+            });
+        })
+        .catch(err => res.redirect('/forgotPassword'));
 };
 
 export let postReset = function (req: Request, res: Response) {
     async.waterfall(
         [
             function (done: any) {
-                User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err: any, user: DashUserModel) {
-                    if (!user || err) {
-                        return res.redirect('back');
-                    }
+                User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } })
+                    .then(user => {
+                        if (!user) return res.redirect('back');
 
-                    req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
-                    req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
+                        req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 });
+                        req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password);
 
-                    if (req.validationErrors()) {
-                        return res.redirect('back');
-                    }
+                        if (req.validationErrors()) return res.redirect('back');
 
-                    user.password = req.body.password;
-                    user.passwordResetToken = undefined;
-                    user.passwordResetExpires = undefined;
+                        user.password = req.body.password;
+                        user.passwordResetToken = undefined;
+                        user.passwordResetExpires = undefined;
 
-                    user.save().then(undefined, (err:any) => {
-                        if (err) {
-                            res.redirect('/login');
-                            return;
-                        }
-                        req.logIn(user, function (err) {
-                            if (err) {
-                                return;
-                            }
-                        });
+                        user.save()
+                            .then(
+                                () => (req as any).logIn(user),
+                                (err: any) => err
+                            )
+                            .catch(err => res.redirect('/login'));
                         done(null, user);
-                    });
-                });
+                    })
+                    .catch(err => res.redirect('back'));
             },
             function (user: DashUserModel, done: any) {
                 const smtpTransport = nodemailer.createTransport({
@@ -286,9 +268,8 @@ export let postReset = function (req: Request, res: Response) {
                     subject: 'Your password has been changed',
                     text: 'Hello,\n\n' + 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n',
                 } as MailOptions;
-                smtpTransport.sendMail(mailOptions, function (err) {
-                    done(null, err);
-                });
+
+                smtpTransport.sendMail(mailOptions, err => done(null, err));
             },
         ],
         function (err) {
diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts
index a1883beab..dbb7a79ed 100644
--- a/src/server/authentication/DashUserModel.ts
+++ b/src/server/authentication/DashUserModel.ts
@@ -2,6 +2,7 @@
 import * as bcrypt from 'bcrypt-nodejs';
 //@ts-ignore
 import * as mongoose from 'mongoose';
+import { Utils } from '../../Utils';
 
 export type DashUserModel = mongoose.Document & {
     email: String;
@@ -25,7 +26,7 @@ export type DashUserModel = mongoose.Document & {
     comparePassword: comparePasswordFunction;
 };
 
-type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void;
+type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => void) => void;
 
 export type AuthToken = {
     accessToken: string;
@@ -63,7 +64,7 @@ const userSchema = new mongoose.Schema(
  * Password hash middleware.
  */
 userSchema.pre('save', function save(next) {
-    const user = this as DashUserModel;
+    const user = this as any as DashUserModel;
     if (!user.isModified('password')) {
         return next();
     }
@@ -101,7 +102,7 @@ export function initializeGuest() {
     new User({
         email: 'guest',
         password: 'guest',
-        userDocumentId: '__guest__',
+        userDocumentId: Utils.GuestID(),
         sharingDocumentId: '2',
         linkDatabaseId: '3',
         cacheDocumentIds: '',
diff --git a/src/server/authentication/Passport.ts b/src/server/authentication/Passport.ts
index d7f891c34..a9cf6698b 100644
--- a/src/server/authentication/Passport.ts
+++ b/src/server/authentication/Passport.ts
@@ -1,6 +1,6 @@
 import * as passport from 'passport';
 import * as passportLocal from 'passport-local';
-import { default as User } from './DashUserModel';
+import { DashUserModel, default as User } from './DashUserModel';
 
 const LocalStrategy = passportLocal.Strategy;
 
@@ -9,21 +9,24 @@ passport.serializeUser<any, any>((req, user, done) => {
 });
 
 passport.deserializeUser<any, any>((id, done) => {
-    User.findById(id, (err: any, user: any) => {
-        done(err, user);
-    });
+    User.findById(id)
+        .exec()
+        .then(user => done(undefined, user));
 });
 
 // AUTHENTICATE JUST WITH EMAIL AND PASSWORD
-passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
-    User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
-        if (error) return done(error);
-        if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
-        user.comparePassword(password, (error: Error, isMatch: boolean) => {
-            if (error) return done(error);
-            if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
-            // valid authentication HERE
-            return done(undefined, user);
-        });
-    });
-}));
\ No newline at end of file
+passport.use(
+    new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
+        User.findOne({ email: email.toLowerCase() })
+            .then(user => {
+                if (!user) return done(undefined, false, { message: 'Invalid email or password' }); // invalid email
+                (user as any as DashUserModel).comparePassword(password, (error: Error, isMatch: boolean) => {
+                    if (error) return done(error);
+                    if (!isMatch) return done(undefined, false, { message: 'Invalid email or password' }); // invalid password
+                    // valid authentication HERE
+                    return done(undefined, user);
+                });
+            })
+            .catch(error => done(error));
+    })
+);
diff --git a/src/server/database.ts b/src/server/database.ts
index 37bc00a85..0893bfd35 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -7,11 +7,14 @@ import { DocumentsCollection, IDatabase } from './IDatabase';
 import { MemoryDatabase } from './MemoryDatabase';
 import { Transferable } from './Message';
 import { Upload } from './SharedMediaTypes';
-import { ObjectId } from 'mongodb';
 
 export namespace Database {
-
     export let disconnect: Function;
+
+    class DocSchema implements mongodb.BSON.Document {
+        _id!: string;
+        id!: string;
+    }
     const schema = 'Dash';
     const port = 27017;
     export const url = `mongodb://localhost:${port}/${schema}`;
@@ -36,11 +39,11 @@ export namespace Database {
                         resolve();
                     });
                     mongoose.connect(url, {
-                        //useNewUrlParser: true, 
+                        //useNewUrlParser: true,
                         dbName: schema,
                         // reconnectTries: Number.MAX_VALUE,
                         // reconnectInterval: 1000,
-                    }); 
+                    });
                 });
             }
         } catch (e) {
@@ -60,10 +63,10 @@ export namespace Database {
         async doConnect() {
             console.error(`\nConnecting to Mongo with URL : ${url}\n`);
             return new Promise<void>(resolve => {
-                this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, }).then(client => {
-                    console.error("mongo connect response\n");
+                this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000 }).then(client => {
+                    console.error('mongo connect response\n');
                     if (!client) {
-                        console.error("\nMongo connect failed with the error:\n");
+                        console.error('\nMongo connect failed with the error:\n');
                         process.exit(0);
                     }
                     this.db = client.db();
@@ -75,18 +78,18 @@ export namespace Database {
 
         public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult) => void, upsert = true, collectionName = DocumentsCollection) {
             if (this.db) {
-                const collection = this.db.collection(collectionName);
+                const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
                 const run = (): Promise<void> => {
                     return new Promise<void>(resolve => {
-                        collection.updateOne({ _id: new ObjectId(id) }, value, { upsert }).then(res => {
-                                if (this.currentWrites[id] === newProm) {
-                                    delete this.currentWrites[id];
-                                }
-                                resolve();
-                                callback(undefined as any, res);
-                            });
+                        collection.updateOne({ _id: id }, value, { upsert }).then(res => {
+                            if (this.currentWrites[id] === newProm) {
+                                delete this.currentWrites[id];
+                            }
+                            resolve();
+                            callback(undefined as any, res);
+                        });
                     });
                 };
                 newProm = prom ? prom.then(run) : run();
@@ -99,18 +102,18 @@ export namespace Database {
 
         public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateResult<mongodb.Document>) => void, upsert = true, collectionName = DocumentsCollection) {
             if (this.db) {
-                const collection = this.db.collection(collectionName);
+                const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
                 const run = (): Promise<void> => {
                     return new Promise<void>(resolve => {
-                        collection.replaceOne({ _id: new ObjectId(id)}, value, { upsert }).then( res => {
-                                if (this.currentWrites[id] === newProm) {
-                                    delete this.currentWrites[id];
-                                }
-                                resolve();
-                                callback(undefined as any, res as any);
-                            });
+                        collection.replaceOne({ _id: id }, value, { upsert }).then(res => {
+                            if (this.currentWrites[id] === newProm) {
+                                delete this.currentWrites[id];
+                            }
+                            resolve();
+                            callback(undefined as any, res as any);
+                        });
                     });
                 };
                 newProm = prom ? prom.then(run) : run();
@@ -135,12 +138,17 @@ export namespace Database {
         public delete(query: any, collectionName?: string): Promise<mongodb.DeleteResult>;
         public delete(id: string, collectionName?: string): Promise<mongodb.DeleteResult>;
         public delete(id: any, collectionName = DocumentsCollection) {
-            if (typeof id === "string") {
-                id = { _id: new ObjectId(id) };
+            if (typeof id === 'string') {
+                id = { _id: id };
             }
             if (this.db) {
                 const db = this.db;
-                return new Promise(res => db.collection(collectionName).deleteMany(id).then(result => res(result)));
+                return new Promise(res =>
+                    db
+                        .collection(collectionName)
+                        .deleteMany(id)
+                        .then(result => res(result))
+                );
             } else {
                 return new Promise(res => this.onConnect.push(() => res(this.delete(id, collectionName))));
             }
@@ -167,12 +175,12 @@ export namespace Database {
 
         public async insert(value: any, collectionName = DocumentsCollection) {
             if (this.db && value !== null) {
-                if ("id" in value) {
+                if ('id' in value) {
                     value._id = value.id;
                     delete value.id;
                 }
                 const id = value._id;
-                const collection = this.db.collection(collectionName);
+                const collection = this.db.collection<DocSchema>(collectionName);
                 const prom = this.currentWrites[id];
                 let newProm: Promise<void>;
                 const run = (): Promise<void> => {
@@ -195,11 +203,12 @@ export namespace Database {
 
         public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) {
             if (this.db) {
-                this.db.collection(collectionName).findOne({ _id: new ObjectId(id) }).then(result => {
+                const collection = this.db.collection<DocSchema>(collectionName);
+                collection.findOne({ _id: id }).then(result => {
                     if (result) {
                         result.id = result._id;
                         //delete result._id;
-                        fn(result.id);
+                        fn(result as any);
                     } else {
                         fn(undefined);
                     }
@@ -209,19 +218,19 @@ export namespace Database {
             }
         }
 
-        public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) {
+        public async getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) {
             if (this.db) {
-                this.db.collection(collectionName).find({ _id: { "$in": ids.map(id => new ObjectId(id)) } }).map(docs => {
-                    // if (err) {
-                    //     console.log(err.message);
-                    //     console.log(err.errmsg);
-                    // }
-                    fn(docs.map((doc:any) => {
+                const found = await this.db
+                    .collection<DocSchema>(collectionName)
+                    .find({ _id: { $in: ids } })
+                    .toArray();
+                fn(
+                    found.map((doc: any) => {
                         doc.id = doc._id;
                         delete doc._id;
                         return doc;
-                    }));
-                });
+                    })
+                );
             } else {
                 this.onConnect.push(() => this.getDocuments(ids, fn, collectionName));
             }
@@ -256,7 +265,7 @@ export namespace Database {
 
         public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.FindCursor> {
             if (this.db) {
-                let cursor = this.db.collection(collectionName).find(query);
+                let cursor = this.db.collection<DocSchema>(collectionName).find(query);
                 if (projection) {
                     cursor = cursor.project(projection);
                 }
@@ -271,7 +280,12 @@ export namespace Database {
         public updateMany(query: any, update: any, collectionName = DocumentsCollection) {
             if (this.db) {
                 const db = this.db;
-                return new Promise<mongodb.UpdateResult>(res => db.collection(collectionName).updateMany(query, update).then(result => res(result)));
+                return new Promise<mongodb.UpdateResult>(res =>
+                    db
+                        .collection(collectionName)
+                        .updateMany(query, update)
+                        .then(result => res(result))
+                );
             } else {
                 return new Promise<mongodb.UpdateResult>(res => {
                     this.onConnect.push(() => this.updateMany(query, update, collectionName).then(res));
@@ -280,13 +294,13 @@ export namespace Database {
         }
 
         public print() {
-            console.log("db says hi!");
+            console.log('db says hi!');
         }
     }
 
     function getDatabase() {
         switch (process.env.DB) {
-            case "MEM":
+            case 'MEM':
                 return new MemoryDatabase();
             default:
                 return new Database();
@@ -301,13 +315,12 @@ export namespace Database {
      * or Dash-internal user data.
      */
     export namespace Auxiliary {
-
         /**
          * All the auxiliary MongoDB collections (schemas)
          */
         export enum AuxiliaryCollections {
-            GooglePhotosUploadHistory = "uploadedFromGooglePhotos",
-            GoogleAccess = "googleAuthentication",
+            GooglePhotosUploadHistory = 'uploadedFromGooglePhotos',
+            GoogleAccess = 'googleAuthentication',
         }
 
         /**
@@ -319,16 +332,18 @@ export namespace Database {
             const cursor = await Instance.query(query, undefined, collection);
             const results = await cursor.toArray();
             const slice = results.slice(0, Math.min(cap, results.length));
-            return removeId ? slice.map((result:any) => {
-                delete result._id;
-                return result;
-            }) : slice;
+            return removeId
+                ? slice.map((result: any) => {
+                      delete result._id;
+                      return result;
+                  })
+                : slice;
         };
 
         /**
          * Searches for the @param query in the specified @param collection,
          * and returns at most the first result. If @param removeId is true,
-         * as it is by default, each object will be stripped of its database id. 
+         * as it is by default, each object will be stripped of its database id.
          * Worth the special case since it converts the Array return type to a single
          * object of the specified type.
          */
@@ -338,7 +353,7 @@ export namespace Database {
         };
 
         /**
-         * Checks to see if an image with the given @param contentSize 
+         * Checks to see if an image with the given @param contentSize
          * already exists in the aux database, i.e. has already been downloaded from Google Photos.
          */
         export const QueryUploadHistory = async (contentSize: number) => {
@@ -352,7 +367,7 @@ export namespace Database {
         export const LogUpload = async (information: Upload.ImageInformation) => {
             const bundle = {
                 _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
-                ...information
+                ...information,
             };
             return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
         };
@@ -362,7 +377,6 @@ export namespace Database {
          * facilitates interactions with all their APIs for a given account.
          */
         export namespace GoogleAccessToken {
-
             /**
              * Format stored in database.
              */
@@ -370,7 +384,7 @@ export namespace Database {
 
             /**
              * Retrieves the credentials associaed with @param userId
-             * and optionally removes their database id according to @param removeId. 
+             * and optionally removes their database id according to @param removeId.
              */
             export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
                 return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
@@ -378,7 +392,7 @@ export namespace Database {
 
             /**
              * Writes the @param enrichedCredentials to the database, associated
-             * with @param userId for later retrieval and updating. 
+             * with @param userId for later retrieval and updating.
              */
             export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
                 return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
@@ -397,7 +411,7 @@ export namespace Database {
             };
 
             /**
-             * Revokes the credentials associated with @param userId. 
+             * Revokes the credentials associated with @param userId.
              */
             export const Revoke = async (userId: string) => {
                 const entry = await Fetch(userId, false);
@@ -405,9 +419,6 @@ export namespace Database {
                     Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess);
                 }
             };
-
         }
-
     }
-
 }
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index af8b8dfdd..a4deaa744 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,13 +1,10 @@
 import * as bodyParser from 'body-parser';
 import { blue, yellow } from 'colors';
-import * as cookieParser from 'cookie-parser';
 import * as cors from 'cors';
 import * as express from 'express';
 import * as session from 'express-session';
 import * as expressValidator from 'express-validator';
-import * as fs from 'fs';
-import { Server as HttpServer } from 'http';
-import { createServer, Server as HttpsServer } from 'https';
+import { createServer } from 'https';
 import * as passport from 'passport';
 import * as request from 'request';
 import * as webpack from 'webpack';
@@ -22,10 +19,10 @@ import { Database } from './database';
 import RouteManager from './RouteManager';
 import RouteSubscriber from './RouteSubscriber';
 import { WebSocket } from './websocket';
-import * as brotli from 'brotli';
 import * as expressFlash from 'express-flash';
 import * as flash from 'connect-flash';
-import * as  MongoStoreConnect from 'connect-mongo';
+import * as brotli from 'brotli';
+import * as MongoStoreConnect from 'connect-mongo';
 import * as config from '../../webpack.config';
 
 /* RouteSetter is a wrapper around the server that prevents the server
@@ -41,29 +38,21 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
     const app = buildWithMiddleware(express());
     const compiler = webpack(config as any);
 
-    app.use(
-        require('webpack-dev-middleware')(compiler, {
-            publicPath: config.output.publicPath,
-        })
-    );
-
-    app.use(require('webpack-hot-middleware')(compiler));
-
     // route table managed by express.  routes are tested sequentially against each of these map rules.  when a match is found, the handler is called to process the request
+    app.use(wdm(compiler, { publicPath: config.output.publicPath }));
+    app.use(whm(compiler));
     app.get(new RegExp(/^\/+$/), (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
     app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); //all urls that start with dash's public directory: /files/  (e.g., /files/images, /files/audio, etc)
     app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
-    app.use(wdm(compiler, { publicPath: config.output.publicPath }));
-    app.use(whm(compiler));
     registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
     registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
     isRelease && !SSL.Loaded && SSL.exit();
     routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
     registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
 
-    let server: HttpServer | HttpsServer;
     isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
-    await new Promise<void>(resolve => (server = isRelease ? createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) : app.listen(resolvedPorts.server, resolve)));
+    const server = isRelease ? createServer(SSL.Credentials, app) : app;
+    await new Promise<void>(resolve => server.listen(resolvedPorts.server, resolve));
     logPort('server', resolvedPorts.server);
 
     resolvedServerUrl = `${isRelease && process.env.serverName ? `https://${process.env.serverName}.com` : 'http://localhost'}:${resolvedPorts.server}`;
@@ -78,26 +67,27 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
 
 const week = 7 * 24 * 60 * 60 * 1000;
 const secret = '64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc';
+const store = process.env.DB === 'MEM' || true ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url });
 
 function buildWithMiddleware(server: express.Express) {
     [
-        cookieParser(),
         session({
             secret,
             resave: true,
             cookie: { maxAge: week },
             saveUninitialized: true,
-            store: process.env.DB === 'MEM' ? new session.MemoryStore() : MongoStoreConnect.create({ mongoUrl: Database.url }),
+            store,
         }),
         flash(),
         expressFlash(),
         bodyParser.json({ limit: '10mb' }),
         bodyParser.urlencoded({ extended: true }),
-        expressValidator.body,
+        expressValidator(), // adds functions (e.g., assert()) to 'req' that help validate the request in the route handling methods
         passport.initialize(),
         passport.session(),
         (req: express.Request, res: express.Response, next: express.NextFunction) => {
             res.locals.user = req.user;
+            console.log('HEADER:' + req.originalUrl + ' path = ' + req.path);
             if ((req.originalUrl.endsWith('.png') || req.originalUrl.endsWith('.jpg') || (process.env.RELEASE === 'true' && req.originalUrl.endsWith('.js'))) && req.method === 'GET') {
                 const period = 30000;
                 res.set('Cache-control', `public, max-age=${period}`);
@@ -108,7 +98,7 @@ function buildWithMiddleware(server: express.Express) {
             next();
         },
     ].forEach(next => server.use(next));
-    
+
     return server;
 }
 
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 4453001b0..a26b81bdf 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -1,10 +1,8 @@
 import { blue } from 'colors';
 import * as express from 'express';
-import { createServer, Server } from 'https';
+import { createServer } from 'https';
+import { Server, Socket } from '../../node_modules/socket.io/dist/index';
 import { networkInterfaces } from 'os';
-import * as sio from 'socket.io';
-import * as _socket from 'socket.io';
-import { Opt } from '../fields/Doc';
 import { Utils } from '../Utils';
 import { logPort } from './ActionUtilities';
 import { timeMap } from './ApiManagers/UserManager';
@@ -18,32 +16,31 @@ import { DocumentsCollection } from './IDatabase';
 import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message';
 import { Search } from './Search';
 import { resolvedPorts } from './server_Initialization';
-var _ = require('lodash');
-type Socket = typeof _socket;
+import * as _ from 'lodash';
 
 export namespace WebSocket {
     export let _socket: Socket;
     export const clients: { [key: string]: Client } = {};
-    export const socketMap = new Map<SocketIO.Socket, string>();
+    export const socketMap = new Map<Socket, string>();
     export const userOperations = new Map<string, number>();
     export let disconnect: Function;
 
     export async function initialize(isRelease: boolean, app: express.Express) {
-        let io: sio.Server;
+        let io: Server;
         if (isRelease) {
             const { socketPort } = process.env;
             if (socketPort) {
                 resolvedPorts.socket = Number(socketPort);
             }
-            let socketEndpoint: Opt<Server>;
-            await new Promise<void>(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)));
-            io = sio(socketEndpoint!, SSL.Credentials as any);
+            io = new Server(createServer(SSL.Credentials, app), SSL.Credentials as any);
+            io.listen(resolvedPorts.socket);
         } else {
-            io = sio().listen(resolvedPorts.socket);
+            io = new Server();
+            io.listen(resolvedPorts.socket);
         }
         logPort('websocket', resolvedPorts.socket);
 
-        io.on('connection', function (socket: Socket) {
+        io.on('connection', socket => {
             _socket = socket;
             socket.use((_packet, next) => {
                 const userEmail = socketMap.get(socket);
@@ -69,7 +66,7 @@ export namespace WebSocket {
                 console.log('Received request to create or join room ' + room);
 
                 const clientsInRoom = socket.rooms.has(room);
-                const numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
+                const numClients = clientsInRoom ? Object.keys(room.sockets).length : 0;
                 console.log('Room ' + room + ' now has ' + numClients + ' client(s)');
 
                 if (numClients === 0) {
@@ -192,7 +189,7 @@ export namespace WebSocket {
         initializeGuest();
     }
 
-    function barReceived(socket: SocketIO.Socket, userEmail: string) {
+    function barReceived(socket: Socket, userEmail: string) {
         clients[userEmail] = new Client(userEmail.toString());
         const currentdate = new Date();
         const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds();
@@ -309,9 +306,9 @@ export namespace WebSocket {
                 if (sendBack) {
                     console.log('Warning: list modified during update. Composite list is being returned.');
                     const id = socket.id;
-                    socket.id = '';
+                    (socket as any).id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
-                    socket.id = id;
+                    (socket as any).id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
             },
@@ -402,9 +399,9 @@ export namespace WebSocket {
                     // the two copies are different, so the server sends its copy.
                     console.log('SEND BACK');
                     const id = socket.id;
-                    socket.id = '';
+                    (socket as any).id = '';
                     socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
-                    socket.id = id;
+                    (socket as any).id = id;
                 } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff);
                 dispatchNextOp(diff.id);
             },
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 884e55fc7..6a1afbb03 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -13,7 +13,6 @@ declare module 'reveal';
 declare module 'react-reveal';
 declare module 'react-reveal/makeCarousel';
 declare module 'react-resizable-rotatable-draggable';
-declare module 'socket.io';
 declare module '@hig/flyout';
 declare module 'uuid/v4';
 declare module 'uuid/v5';
-- 
cgit v1.2.3-70-g09d2


From 0adf05851b86b54b4414cad28b2975f533e45b9f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 7 Dec 2023 15:33:57 -0500
Subject: fixed table boxes and label boxes.

---
 .../views/nodes/DataVizBox/components/TableBox.tsx |  8 ++++--
 src/client/views/nodes/LabelBox.tsx                | 32 ++++++++++++----------
 2 files changed, 24 insertions(+), 16 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index d422a7536..0e766e5f0 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,5 +1,5 @@
 import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
@@ -12,7 +12,7 @@ import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
 //import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
-const DATA_VIZ_TABLE_ROW_HEIGHT = '30px';
+const DATA_VIZ_TABLE_ROW_HEIGHT = '30';
 
 interface TableBoxProps {
     Document: Doc;
@@ -80,12 +80,14 @@ export class TableBox extends React.Component<TableBoxProps> {
         return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
     }
     @computed get rowHeight() {
+        console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
         return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
     }
     @computed get startID() {
         return this.rowHeight ? Math.floor(this._scrollTop / this.rowHeight) : 0;
     }
     @computed get endID() {
+        console.log('start = ' + this.startID + '  container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
         return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
     }
     @action handleScroll = () => {
@@ -160,6 +162,8 @@ export class TableBox extends React.Component<TableBoxProps> {
     };
 
     render() {
+        console.log(this.endID);
+        trace();
         if (this._tableData.length > 0) {
             return (
                 <div
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 71f9c000b..f8140af93 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -84,18 +84,22 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
     }
 
-    fitTextToBox = (r: any): NodeJS.Timeout | {
-        rotateText: null;
-        fontSizeFactor: number;
-        minimumFontSize: number;
-        maximumFontSize: number;
-        limitingDimension: string;
-        horizontalAlign: string;
-        verticalAlign: string;
-        textAlign: string;
-        singleLine: boolean;
-        whiteSpace: string;
-    } => {
+    fitTextToBox = (
+        r: any
+    ):
+        | NodeJS.Timeout
+        | {
+              rotateText: null;
+              fontSizeFactor: number;
+              minimumFontSize: number;
+              maximumFontSize: number;
+              limitingDimension: string;
+              horizontalAlign: string;
+              verticalAlign: string;
+              textAlign: string;
+              singleLine: boolean;
+              whiteSpace: string;
+          } => {
         const singleLine = BoolCast(this.layoutDoc._singleLine, true);
         const params = {
             rotateText: null,
@@ -154,9 +158,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
                         paddingBottom: NumCast(this.layoutDoc._yPadding),
                         width: this.props.PanelWidth(),
                         height: this.props.PanelHeight(),
-                        whiteSpace:  !(boxParams instanceof NodeJS.Timeout) && boxParams.singleLine ? 'pre' : 'pre-wrap',
+                        whiteSpace: 'singleLine' in boxParams && boxParams.singleLine ? 'pre' : 'pre-wrap',
                     }}>
-                    <span style={{ width: !(boxParams instanceof NodeJS.Timeout) && boxParams.singleLine ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
+                    <span style={{ width: 'singleLine' in boxParams ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
                         {label.startsWith('#') ? null : label.replace(/([^a-zA-Z])/g, '$1\u200b')}
                     </span>
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 380ee1acac1c0b7972d7d423cf804af146dc0edf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 10 Dec 2023 20:19:27 -0500
Subject: massive changes to use mobx 6 which means not accessing props
 directly in @computed functions.

---
 package-lock.json                                  | 2762 +++++++++++++++++---
 package.json                                       |   13 +-
 src/.babelrc                                       |    3 +
 src/Utils.ts                                       |    7 +
 src/client/DocServer.ts                            |    2 +-
 src/client/documents/DocumentTypes.ts              |    1 -
 src/client/documents/Documents.ts                  |   13 +-
 src/client/util/DocumentManager.ts                 |   11 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |    3 +-
 src/client/util/LinkManager.ts                     |   14 +-
 src/client/util/RTFMarkup.tsx                      |    2 +-
 src/client/util/SelectionManager.ts                |   20 +-
 src/client/util/ServerStats.tsx                    |    6 +-
 src/client/util/SettingsManager.tsx                |    8 +-
 src/client/util/SharingManager.tsx                 |    4 +-
 src/client/util/SnappingManager.ts                 |    2 +-
 src/client/views/AntimodeMenu.tsx                  |   19 +-
 src/client/views/AudioWaveform.scss                |   17 -
 src/client/views/AudioWaveform.tsx                 |  117 -
 src/client/views/ContextMenu.tsx                   |   99 +-
 src/client/views/ContextMenuItem.tsx               |   20 +-
 src/client/views/DocComponent.tsx                  |   61 +-
 src/client/views/DocumentButtonBar.tsx             |   61 +-
 src/client/views/DocumentDecorations.tsx           |   74 +-
 src/client/views/EditableView.tsx                  |  111 +-
 src/client/views/InkStrokeProperties.ts            |    2 +-
 src/client/views/InkingStroke.tsx                  |    4 +-
 src/client/views/MainView.tsx                      |   19 +-
 src/client/views/MainViewModal.tsx                 |    2 -
 src/client/views/MarqueeAnnotator.tsx              |   19 +-
 src/client/views/PreviewCursor.tsx                 |  135 +-
 src/client/views/PropertiesView.tsx                |   53 +-
 src/client/views/StyleProvider.tsx                 |    4 +-
 .../views/collections/CollectionDockingView.tsx    |   45 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |  101 +-
 src/client/views/collections/CollectionMenu.tsx    |   45 +-
 .../views/collections/CollectionNoteTakingView.tsx |    1 -
 .../views/collections/CollectionPileView.tsx       |    2 +-
 .../collections/CollectionStackedTimeline.tsx      |  217 +-
 .../views/collections/CollectionStackingView.tsx   |  187 +-
 .../CollectionStackingViewFieldColumn.tsx          |  139 +-
 src/client/views/collections/CollectionSubView.tsx |   92 +-
 .../views/collections/CollectionTreeView.tsx       |  192 +-
 src/client/views/collections/CollectionView.tsx    |   53 +-
 src/client/views/collections/TabDocView.tsx        |   91 +-
 src/client/views/collections/TreeView.tsx          |  505 ++--
 .../CollectionFreeFormInfoState.tsx                |   16 +-
 .../CollectionFreeFormInfoUI.tsx                   |   38 +-
 .../CollectionFreeFormLinkView.tsx                 |    4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  376 +--
 .../collections/collectionFreeForm/MarqueeView.tsx |  142 +-
 .../collectionGrid/CollectionGridView.tsx          |    2 -
 .../collectionLinear/CollectionLinearView.tsx      |   44 +-
 .../CollectionMulticolumnView.tsx                  |    1 -
 .../CollectionMultirowView.tsx                     |    1 -
 .../collectionSchema/CollectionSchemaView.tsx      |   10 +-
 .../collectionSchema/SchemaColumnHeader.tsx        |    2 +-
 .../collections/collectionSchema/SchemaRowBox.tsx  |    4 +-
 .../collectionSchema/SchemaTableCell.tsx           |  141 +-
 src/client/views/global/globalScripts.ts           |    8 +-
 src/client/views/linking/LinkMenu.tsx              |    8 +-
 src/client/views/linking/LinkMenuItem.tsx          |   96 +-
 src/client/views/nodes/AudioBox.tsx                |   75 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  109 +-
 src/client/views/nodes/ColorBox.scss               |   22 -
 src/client/views/nodes/ColorBox.tsx                |   88 -
 .../views/nodes/DataVizBox/components/TableBox.tsx |    4 +-
 src/client/views/nodes/DocumentContentsView.tsx    |   60 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  106 +-
 src/client/views/nodes/DocumentView.tsx            |  419 +--
 src/client/views/nodes/EquationBox.tsx             |   34 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |   32 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |   37 +-
 src/client/views/nodes/ImageBox.tsx                |  198 +-
 src/client/views/nodes/KeyValueBox.tsx             |   38 +-
 src/client/views/nodes/KeyValuePair.tsx            |   37 +-
 src/client/views/nodes/LabelBox.tsx                |   29 +-
 src/client/views/nodes/LinkBox.tsx                 |    2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |    4 +-
 src/client/views/nodes/LoadingBox.tsx              |   14 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   84 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |    2 +-
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   14 +-
 src/client/views/nodes/PDFBox.tsx                  |   95 +-
 src/client/views/nodes/ScreenshotBox.tsx           |    2 +-
 src/client/views/nodes/VideoBox.tsx                |  123 +-
 src/client/views/nodes/WebBox.tsx                  |    4 +-
 src/client/views/nodes/audio/AudioWaveform.scss    |   17 +
 src/client/views/nodes/audio/AudioWaveform.tsx     |  127 +
 src/client/views/nodes/audio/WaveCanvas.tsx        |  100 +
 .../views/nodes/formattedText/DashDocView.tsx      |    2 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   14 +-
 .../views/nodes/formattedText/EquationEditor.scss  |  468 ++++
 .../views/nodes/formattedText/EquationEditor.tsx   |    8 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  213 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   48 +-
 src/client/views/nodes/trails/PresBox.tsx          |   61 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |   64 +-
 src/client/views/pdf/AnchorMenu.tsx                |   24 +-
 src/client/views/pdf/PDFViewer.tsx                 |  151 +-
 src/client/views/topbar/TopBar.tsx                 |    1 -
 src/fields/Doc.ts                                  |   40 +-
 src/fields/List.ts                                 |    5 +-
 src/fields/util.ts                                 |    2 +-
 src/server/ApiManagers/GooglePhotosManager.ts      |  155 +-
 src/server/DashUploadUtils.ts                      |   27 +-
 tsconfig.json                                      |    2 +-
 webpack.config.js                                  |    7 +-
 108 files changed, 6103 insertions(+), 3316 deletions(-)
 create mode 100644 src/.babelrc
 delete mode 100644 src/client/views/AudioWaveform.scss
 delete mode 100644 src/client/views/AudioWaveform.tsx
 delete mode 100644 src/client/views/nodes/ColorBox.scss
 delete mode 100644 src/client/views/nodes/ColorBox.tsx
 create mode 100644 src/client/views/nodes/audio/AudioWaveform.scss
 create mode 100644 src/client/views/nodes/audio/AudioWaveform.tsx
 create mode 100644 src/client/views/nodes/audio/WaveCanvas.tsx
 create mode 100644 src/client/views/nodes/formattedText/EquationEditor.scss

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index 8cbd29629..9afc22828 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
             "version": "1.0.0",
             "dependencies": {
                 "@azure/storage-blob": "^12.17.0",
+                "@babel/preset-env": "^7.23.5",
+                "@babel/preset-react": "^7.23.3",
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
                 "@ffmpeg/core": "0.12.4",
@@ -41,6 +43,7 @@
                 "async": "^3.2.5",
                 "axios": "^1.6.2",
                 "babel": "^6.23.0",
+                "babel-loader": "^9.1.3",
                 "bcrypt-nodejs": "0.0.3",
                 "bezier-curve": "^1.0.0",
                 "bezier-js": "^6.1.4",
@@ -55,7 +58,6 @@
                 "canvas": "^2.11.2",
                 "chart.js": "^4.4.0",
                 "child_process": "^1.0.2",
-                "chrome": "^0.1.0",
                 "class-transformer": "^0.5.1",
                 "color": "^4.2.3",
                 "colors": "^1.4.0",
@@ -105,6 +107,7 @@
                 "image-size": "^1.0.2",
                 "image-size-stream": "^1.1.0",
                 "jimp": "^0.22.10",
+                "jpeg-autorotate": "^9.0.0",
                 "jquery": "^3.7.1",
                 "js-datepicker": "^5.18.2",
                 "jsonschema": "^1.4.1",
@@ -114,9 +117,9 @@
                 "md5-file": "^5.0.0",
                 "memorystream": "^0.3.1",
                 "mobile-detect": "^1.4.5",
-                "mobx": "^5.15.7",
-                "mobx-react": "^5.4.4",
-                "mobx-utils": "^5.6.2",
+                "mobx": "^6.12.0",
+                "mobx-react": "^9.1.0",
+                "mobx-utils": "^6.0.8",
                 "mongodb": "^6.3.0",
                 "mongoose": "^8.0.2",
                 "node-stream-zip": "^1.15.0",
@@ -151,7 +154,6 @@
                 "raw-loader": "^4.0.2",
                 "rc-switch": "^4.1.0",
                 "react": "^18.2.0",
-                "react-audio-waveform": "0.0.5",
                 "react-autosuggest": "^10.1.0",
                 "react-color": "^2.19.3",
                 "react-compound-slider": "^3.4.0",
@@ -164,7 +166,6 @@
                 "react-markdown": "^9.0.1",
                 "react-measure": "^2.5.2",
                 "react-resizable": "^3.0.5",
-                "react-reveal": "^1.2.2",
                 "react-select": "^5.8.0",
                 "readline": "^1.3.0",
                 "recharts": "^2.10.3",
@@ -288,271 +289,1867 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/@ampproject/remapping": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
+            "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
+            "peer": true,
+            "dependencies": {
+                "@jridgewell/gen-mapping": "^0.3.0",
+                "@jridgewell/trace-mapping": "^0.3.9"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
         "node_modules/@azure/abort-controller": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
             "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
+        "node_modules/@azure/core-auth": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz",
+            "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
+        "node_modules/@azure/core-http": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-3.0.2.tgz",
+            "integrity": "sha512-o1wR9JrmoM0xEAa0Ue7Sp8j+uJvmqYaGoHOCT5qaVYmvgmnZDC0OvQimPA/JR3u77Sz6D1y3Xmk1y69cDU9q9A==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/core-auth": "^1.3.0",
+                "@azure/core-tracing": "1.0.0-preview.13",
+                "@azure/core-util": "^1.1.1",
+                "@azure/logger": "^1.0.0",
+                "@types/node-fetch": "^2.5.0",
+                "@types/tunnel": "^0.0.3",
+                "form-data": "^4.0.0",
+                "node-fetch": "^2.6.7",
+                "process": "^0.11.10",
+                "tslib": "^2.2.0",
+                "tunnel": "^0.0.6",
+                "uuid": "^8.3.0",
+                "xml2js": "^0.5.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@azure/core-http/node_modules/form-data": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+            "dependencies": {
+                "asynckit": "^0.4.0",
+                "combined-stream": "^1.0.8",
+                "mime-types": "^2.1.12"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/@azure/core-http/node_modules/uuid": {
+            "version": "8.3.2",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+            "bin": {
+                "uuid": "dist/bin/uuid"
+            }
+        },
+        "node_modules/@azure/core-lro": {
+            "version": "2.5.3",
+            "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz",
+            "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/core-util": "^1.2.0",
+                "@azure/logger": "^1.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@azure/core-paging": {
+            "version": "1.5.0",
+            "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
+            "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+            "dependencies": {
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@azure/core-tracing": {
+            "version": "1.0.0-preview.13",
+            "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz",
+            "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==",
+            "dependencies": {
+                "@opentelemetry/api": "^1.0.1",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
+        "node_modules/@azure/core-util": {
+            "version": "1.3.2",
+            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
+            "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@azure/logger": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
+            "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
+            "dependencies": {
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@azure/storage-blob": {
+            "version": "12.17.0",
+            "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz",
+            "integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/core-http": "^3.0.0",
+                "@azure/core-lro": "^2.2.0",
+                "@azure/core-paging": "^1.1.1",
+                "@azure/core-tracing": "1.0.0-preview.13",
+                "@azure/logger": "^1.0.0",
+                "events": "^3.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "node_modules/@babel/code-frame": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+            "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+            "dependencies": {
+                "@babel/highlight": "^7.23.4",
+                "chalk": "^2.4.2"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/compat-data": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
+            "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/core": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.5.tgz",
+            "integrity": "sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==",
+            "peer": true,
+            "dependencies": {
+                "@ampproject/remapping": "^2.2.0",
+                "@babel/code-frame": "^7.23.5",
+                "@babel/generator": "^7.23.5",
+                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-module-transforms": "^7.23.3",
+                "@babel/helpers": "^7.23.5",
+                "@babel/parser": "^7.23.5",
+                "@babel/template": "^7.22.15",
+                "@babel/traverse": "^7.23.5",
+                "@babel/types": "^7.23.5",
+                "convert-source-map": "^2.0.0",
+                "debug": "^4.1.0",
+                "gensync": "^1.0.0-beta.2",
+                "json5": "^2.2.3",
+                "semver": "^6.3.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/babel"
+            }
+        },
+        "node_modules/@babel/core/node_modules/convert-source-map": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+            "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+            "peer": true
+        },
+        "node_modules/@babel/core/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "peer": true,
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@babel/core/node_modules/json5": {
+            "version": "2.2.3",
+            "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+            "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+            "peer": true,
+            "bin": {
+                "json5": "lib/cli.js"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/@babel/core/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+            "peer": true
+        },
+        "node_modules/@babel/core/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+            "peer": true,
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/@babel/generator": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
+            "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
+            "dependencies": {
+                "@babel/types": "^7.23.5",
+                "@jridgewell/gen-mapping": "^0.3.2",
+                "@jridgewell/trace-mapping": "^0.3.17",
+                "jsesc": "^2.5.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-annotate-as-pure": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+            "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
+            "version": "7.22.15",
+            "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz",
+            "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==",
+            "dependencies": {
+                "@babel/types": "^7.22.15"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-compilation-targets": {
+            "version": "7.22.15",
+            "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
+            "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
+            "dependencies": {
+                "@babel/compat-data": "^7.22.9",
+                "@babel/helper-validator-option": "^7.22.15",
+                "browserslist": "^4.21.9",
+                "lru-cache": "^5.1.1",
+                "semver": "^6.3.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+            "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+            "dependencies": {
+                "yallist": "^3.0.2"
+            }
+        },
+        "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+            "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+        },
+        "node_modules/@babel/helper-create-class-features-plugin": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz",
+            "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==",
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-function-name": "^7.23.0",
+                "@babel/helper-member-expression-to-functions": "^7.23.0",
+                "@babel/helper-optimise-call-expression": "^7.22.5",
+                "@babel/helper-replace-supers": "^7.22.20",
+                "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+                "@babel/helper-split-export-declaration": "^7.22.6",
+                "semver": "^6.3.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/@babel/helper-create-regexp-features-plugin": {
+            "version": "7.22.15",
+            "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
+            "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==",
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "regexpu-core": "^5.3.1",
+                "semver": "^6.3.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/@babel/helper-define-polyfill-provider": {
+            "version": "0.4.3",
+            "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz",
+            "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==",
+            "dependencies": {
+                "@babel/helper-compilation-targets": "^7.22.6",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "debug": "^4.1.1",
+                "lodash.debounce": "^4.0.8",
+                "resolve": "^1.14.2"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+            }
+        },
+        "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/@babel/helper-environment-visitor": {
+            "version": "7.22.20",
+            "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+            "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-function-name": {
+            "version": "7.23.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+            "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+            "dependencies": {
+                "@babel/template": "^7.22.15",
+                "@babel/types": "^7.23.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-hoist-variables": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+            "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-member-expression-to-functions": {
+            "version": "7.23.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
+            "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
+            "dependencies": {
+                "@babel/types": "^7.23.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-module-imports": {
+            "version": "7.22.15",
+            "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+            "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
+            "dependencies": {
+                "@babel/types": "^7.22.15"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-module-transforms": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
+            "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+            "dependencies": {
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-module-imports": "^7.22.15",
+                "@babel/helper-simple-access": "^7.22.5",
+                "@babel/helper-split-export-declaration": "^7.22.6",
+                "@babel/helper-validator-identifier": "^7.22.20"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/helper-optimise-call-expression": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+            "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-plugin-utils": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
+            "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-remap-async-to-generator": {
+            "version": "7.22.20",
+            "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz",
+            "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==",
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-wrap-function": "^7.22.20"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/helper-replace-supers": {
+            "version": "7.22.20",
+            "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz",
+            "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==",
+            "dependencies": {
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-member-expression-to-functions": "^7.22.15",
+                "@babel/helper-optimise-call-expression": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/helper-simple-access": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+            "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+            "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-split-export-declaration": {
+            "version": "7.22.6",
+            "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+            "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+            "dependencies": {
+                "@babel/types": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-string-parser": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
+            "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-validator-identifier": {
+            "version": "7.22.20",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+            "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-validator-option": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
+            "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helper-wrap-function": {
+            "version": "7.22.20",
+            "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
+            "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
+            "dependencies": {
+                "@babel/helper-function-name": "^7.22.5",
+                "@babel/template": "^7.22.15",
+                "@babel/types": "^7.22.19"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/helpers": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz",
+            "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==",
+            "peer": true,
+            "dependencies": {
+                "@babel/template": "^7.22.15",
+                "@babel/traverse": "^7.23.5",
+                "@babel/types": "^7.23.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/highlight": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+            "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+            "dependencies": {
+                "@babel/helper-validator-identifier": "^7.22.20",
+                "chalk": "^2.4.2",
+                "js-tokens": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@babel/parser": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
+            "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
+            "bin": {
+                "parser": "bin/babel-parser.js"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz",
+            "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz",
+            "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+                "@babel/plugin-transform-optional-chaining": "^7.23.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.13.0"
+            }
+        },
+        "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz",
+            "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==",
+            "dependencies": {
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/plugin-proposal-private-property-in-object": {
+            "version": "7.21.0-placeholder-for-preset-env.2",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+            "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-async-generators": {
+            "version": "7.8.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+            "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-class-properties": {
+            "version": "7.12.13",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+            "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.12.13"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-class-static-block": {
+            "version": "7.14.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+            "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-dynamic-import": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+            "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-export-namespace-from": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+            "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.3"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-import-assertions": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz",
+            "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-import-attributes": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz",
+            "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-import-meta": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+            "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.10.4"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-json-strings": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+            "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-jsx": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
+            "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+            "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.10.4"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+            "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-numeric-separator": {
+            "version": "7.10.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+            "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.10.4"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-object-rest-spread": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+            "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+            "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-optional-chaining": {
+            "version": "7.8.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+            "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.8.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-private-property-in-object": {
+            "version": "7.14.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+            "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-top-level-await": {
+            "version": "7.14.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+            "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+            "version": "7.18.6",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+            "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+            "dependencies": {
+                "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+                "@babel/helper-plugin-utils": "^7.18.6"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-arrow-functions": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz",
+            "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-async-generator-functions": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz",
+            "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==",
+            "dependencies": {
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-remap-async-to-generator": "^7.22.20",
+                "@babel/plugin-syntax-async-generators": "^7.8.4"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-async-to-generator": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz",
+            "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==",
+            "dependencies": {
+                "@babel/helper-module-imports": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-remap-async-to-generator": "^7.22.20"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-block-scoped-functions": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz",
+            "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-block-scoping": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz",
+            "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-class-properties": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz",
+            "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==",
+            "dependencies": {
+                "@babel/helper-create-class-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-class-static-block": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz",
+            "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==",
+            "dependencies": {
+                "@babel/helper-create-class-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-class-static-block": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.12.0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-classes": {
+            "version": "7.23.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz",
+            "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==",
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-function-name": "^7.23.0",
+                "@babel/helper-optimise-call-expression": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-replace-supers": "^7.22.20",
+                "@babel/helper-split-export-declaration": "^7.22.6",
+                "globals": "^11.1.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-computed-properties": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz",
+            "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/template": "^7.22.15"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-destructuring": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz",
+            "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-dotall-regex": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz",
+            "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==",
+            "dependencies": {
+                "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-duplicate-keys": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz",
+            "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-dynamic-import": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz",
+            "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-dynamic-import": "^7.8.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-exponentiation-operator": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz",
+            "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==",
+            "dependencies": {
+                "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-export-namespace-from": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz",
+            "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-for-of": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz",
+            "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-function-name": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz",
+            "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==",
+            "dependencies": {
+                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-function-name": "^7.23.0",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-json-strings": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz",
+            "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-json-strings": "^7.8.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-literals": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz",
+            "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz",
+            "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-member-expression-literals": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz",
+            "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-modules-amd": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz",
+            "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==",
+            "dependencies": {
+                "@babel/helper-module-transforms": "^7.23.3",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-modules-commonjs": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz",
+            "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==",
+            "dependencies": {
+                "@babel/helper-module-transforms": "^7.23.3",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-simple-access": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-modules-systemjs": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz",
+            "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==",
+            "dependencies": {
+                "@babel/helper-hoist-variables": "^7.22.5",
+                "@babel/helper-module-transforms": "^7.23.3",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-validator-identifier": "^7.22.20"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-modules-umd": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz",
+            "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==",
+            "dependencies": {
+                "@babel/helper-module-transforms": "^7.23.3",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
+            "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
+            "dependencies": {
+                "@babel/helper-create-regexp-features-plugin": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-new-target": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz",
+            "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz",
+            "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-numeric-separator": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz",
+            "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-object-rest-spread": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
+            "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
+            "dependencies": {
+                "@babel/compat-data": "^7.23.3",
+                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+                "@babel/plugin-transform-parameters": "^7.23.3"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-object-super": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz",
+            "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-replace-supers": "^7.22.20"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/plugin-transform-optional-catch-binding": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz",
+            "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
             },
             "engines": {
-                "node": ">=12.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-auth": {
-            "version": "1.4.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz",
-            "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==",
+        "node_modules/@babel/plugin-transform-optional-chaining": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz",
+            "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==",
             "dependencies": {
-                "@azure/abort-controller": "^1.0.0",
-                "tslib": "^2.2.0"
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+                "@babel/plugin-syntax-optional-chaining": "^7.8.3"
             },
             "engines": {
-                "node": ">=12.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-http": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-3.0.2.tgz",
-            "integrity": "sha512-o1wR9JrmoM0xEAa0Ue7Sp8j+uJvmqYaGoHOCT5qaVYmvgmnZDC0OvQimPA/JR3u77Sz6D1y3Xmk1y69cDU9q9A==",
+        "node_modules/@babel/plugin-transform-parameters": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz",
+            "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==",
             "dependencies": {
-                "@azure/abort-controller": "^1.0.0",
-                "@azure/core-auth": "^1.3.0",
-                "@azure/core-tracing": "1.0.0-preview.13",
-                "@azure/core-util": "^1.1.1",
-                "@azure/logger": "^1.0.0",
-                "@types/node-fetch": "^2.5.0",
-                "@types/tunnel": "^0.0.3",
-                "form-data": "^4.0.0",
-                "node-fetch": "^2.6.7",
-                "process": "^0.11.10",
-                "tslib": "^2.2.0",
-                "tunnel": "^0.0.6",
-                "uuid": "^8.3.0",
-                "xml2js": "^0.5.0"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-http/node_modules/form-data": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+        "node_modules/@babel/plugin-transform-private-methods": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz",
+            "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==",
             "dependencies": {
-                "asynckit": "^0.4.0",
-                "combined-stream": "^1.0.8",
-                "mime-types": "^2.1.12"
+                "@babel/helper-create-class-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
-                "node": ">= 6"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-http/node_modules/uuid": {
-            "version": "8.3.2",
-            "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-            "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
-            "bin": {
-                "uuid": "dist/bin/uuid"
+        "node_modules/@babel/plugin-transform-private-property-in-object": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz",
+            "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==",
+            "dependencies": {
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-create-class-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-lro": {
-            "version": "2.5.3",
-            "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz",
-            "integrity": "sha512-ubkOf2YCnVtq7KqEJQqAI8dDD5rH1M6OP5kW0KO/JQyTaxLA0N0pjFWvvaysCj9eHMNBcuuoZXhhl0ypjod2DA==",
+        "node_modules/@babel/plugin-transform-property-literals": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz",
+            "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==",
             "dependencies": {
-                "@azure/abort-controller": "^1.0.0",
-                "@azure/core-util": "^1.2.0",
-                "@azure/logger": "^1.0.0",
-                "tslib": "^2.2.0"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-paging": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz",
-            "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==",
+        "node_modules/@babel/plugin-transform-react-display-name": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz",
+            "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-tracing": {
-            "version": "1.0.0-preview.13",
-            "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz",
-            "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==",
+        "node_modules/@babel/plugin-transform-react-jsx": {
+            "version": "7.23.4",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz",
+            "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==",
             "dependencies": {
-                "@opentelemetry/api": "^1.0.1",
-                "tslib": "^2.2.0"
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-module-imports": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/plugin-syntax-jsx": "^7.23.3",
+                "@babel/types": "^7.23.4"
             },
             "engines": {
-                "node": ">=12.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/core-util": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
-            "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
+        "node_modules/@babel/plugin-transform-react-jsx-development": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz",
+            "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==",
             "dependencies": {
-                "@azure/abort-controller": "^1.0.0",
-                "tslib": "^2.2.0"
+                "@babel/plugin-transform-react-jsx": "^7.22.5"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/logger": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
-            "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
+        "node_modules/@babel/plugin-transform-react-pure-annotations": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz",
+            "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==",
             "dependencies": {
-                "tslib": "^2.2.0"
+                "@babel/helper-annotate-as-pure": "^7.22.5",
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@azure/storage-blob": {
-            "version": "12.17.0",
-            "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz",
-            "integrity": "sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ==",
+        "node_modules/@babel/plugin-transform-regenerator": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz",
+            "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==",
             "dependencies": {
-                "@azure/abort-controller": "^1.0.0",
-                "@azure/core-http": "^3.0.0",
-                "@azure/core-lro": "^2.2.0",
-                "@azure/core-paging": "^1.1.1",
-                "@azure/core-tracing": "1.0.0-preview.13",
-                "@azure/logger": "^1.0.0",
-                "events": "^3.0.0",
-                "tslib": "^2.2.0"
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "regenerator-transform": "^0.15.2"
             },
             "engines": {
-                "node": ">=14.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/code-frame": {
-            "version": "7.23.5",
-            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
-            "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+        "node_modules/@babel/plugin-transform-reserved-words": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz",
+            "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==",
             "dependencies": {
-                "@babel/highlight": "^7.23.4",
-                "chalk": "^2.4.2"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/generator": {
-            "version": "7.23.5",
-            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz",
-            "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==",
+        "node_modules/@babel/plugin-transform-shorthand-properties": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz",
+            "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==",
             "dependencies": {
-                "@babel/types": "^7.23.5",
-                "@jridgewell/gen-mapping": "^0.3.2",
-                "@jridgewell/trace-mapping": "^0.3.17",
-                "jsesc": "^2.5.1"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-environment-visitor": {
-            "version": "7.22.20",
-            "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
-            "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
+        "node_modules/@babel/plugin-transform-spread": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz",
+            "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
+            },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-function-name": {
-            "version": "7.23.0",
-            "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
-            "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
+        "node_modules/@babel/plugin-transform-sticky-regex": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz",
+            "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==",
             "dependencies": {
-                "@babel/template": "^7.22.15",
-                "@babel/types": "^7.23.0"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-hoist-variables": {
-            "version": "7.22.5",
-            "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
-            "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
+        "node_modules/@babel/plugin-transform-template-literals": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz",
+            "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==",
             "dependencies": {
-                "@babel/types": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-module-imports": {
-            "version": "7.22.5",
-            "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
-            "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
+        "node_modules/@babel/plugin-transform-typeof-symbol": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz",
+            "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==",
             "dependencies": {
-                "@babel/types": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-split-export-declaration": {
-            "version": "7.22.6",
-            "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
-            "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+        "node_modules/@babel/plugin-transform-unicode-escapes": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz",
+            "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==",
             "dependencies": {
-                "@babel/types": "^7.22.5"
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-string-parser": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
-            "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
+        "node_modules/@babel/plugin-transform-unicode-property-regex": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz",
+            "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==",
+            "dependencies": {
+                "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/helper-validator-identifier": {
-            "version": "7.22.20",
-            "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-            "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+        "node_modules/@babel/plugin-transform-unicode-regex": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz",
+            "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==",
+            "dependencies": {
+                "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
+            },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
-        "node_modules/@babel/highlight": {
-            "version": "7.23.4",
-            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
-            "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+        "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz",
+            "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==",
             "dependencies": {
-                "@babel/helper-validator-identifier": "^7.22.20",
-                "chalk": "^2.4.2",
-                "js-tokens": "^4.0.0"
+                "@babel/helper-create-regexp-features-plugin": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5"
             },
             "engines": {
                 "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0"
             }
         },
-        "node_modules/@babel/parser": {
+        "node_modules/@babel/preset-env": {
             "version": "7.23.5",
-            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz",
-            "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==",
+            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.5.tgz",
+            "integrity": "sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A==",
+            "dependencies": {
+                "@babel/compat-data": "^7.23.5",
+                "@babel/helper-compilation-targets": "^7.22.15",
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-validator-option": "^7.23.5",
+                "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
+                "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
+                "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3",
+                "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+                "@babel/plugin-syntax-async-generators": "^7.8.4",
+                "@babel/plugin-syntax-class-properties": "^7.12.13",
+                "@babel/plugin-syntax-class-static-block": "^7.14.5",
+                "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+                "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+                "@babel/plugin-syntax-import-assertions": "^7.23.3",
+                "@babel/plugin-syntax-import-attributes": "^7.23.3",
+                "@babel/plugin-syntax-import-meta": "^7.10.4",
+                "@babel/plugin-syntax-json-strings": "^7.8.3",
+                "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+                "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+                "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+                "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+                "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+                "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+                "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+                "@babel/plugin-syntax-top-level-await": "^7.14.5",
+                "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+                "@babel/plugin-transform-arrow-functions": "^7.23.3",
+                "@babel/plugin-transform-async-generator-functions": "^7.23.4",
+                "@babel/plugin-transform-async-to-generator": "^7.23.3",
+                "@babel/plugin-transform-block-scoped-functions": "^7.23.3",
+                "@babel/plugin-transform-block-scoping": "^7.23.4",
+                "@babel/plugin-transform-class-properties": "^7.23.3",
+                "@babel/plugin-transform-class-static-block": "^7.23.4",
+                "@babel/plugin-transform-classes": "^7.23.5",
+                "@babel/plugin-transform-computed-properties": "^7.23.3",
+                "@babel/plugin-transform-destructuring": "^7.23.3",
+                "@babel/plugin-transform-dotall-regex": "^7.23.3",
+                "@babel/plugin-transform-duplicate-keys": "^7.23.3",
+                "@babel/plugin-transform-dynamic-import": "^7.23.4",
+                "@babel/plugin-transform-exponentiation-operator": "^7.23.3",
+                "@babel/plugin-transform-export-namespace-from": "^7.23.4",
+                "@babel/plugin-transform-for-of": "^7.23.3",
+                "@babel/plugin-transform-function-name": "^7.23.3",
+                "@babel/plugin-transform-json-strings": "^7.23.4",
+                "@babel/plugin-transform-literals": "^7.23.3",
+                "@babel/plugin-transform-logical-assignment-operators": "^7.23.4",
+                "@babel/plugin-transform-member-expression-literals": "^7.23.3",
+                "@babel/plugin-transform-modules-amd": "^7.23.3",
+                "@babel/plugin-transform-modules-commonjs": "^7.23.3",
+                "@babel/plugin-transform-modules-systemjs": "^7.23.3",
+                "@babel/plugin-transform-modules-umd": "^7.23.3",
+                "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
+                "@babel/plugin-transform-new-target": "^7.23.3",
+                "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
+                "@babel/plugin-transform-numeric-separator": "^7.23.4",
+                "@babel/plugin-transform-object-rest-spread": "^7.23.4",
+                "@babel/plugin-transform-object-super": "^7.23.3",
+                "@babel/plugin-transform-optional-catch-binding": "^7.23.4",
+                "@babel/plugin-transform-optional-chaining": "^7.23.4",
+                "@babel/plugin-transform-parameters": "^7.23.3",
+                "@babel/plugin-transform-private-methods": "^7.23.3",
+                "@babel/plugin-transform-private-property-in-object": "^7.23.4",
+                "@babel/plugin-transform-property-literals": "^7.23.3",
+                "@babel/plugin-transform-regenerator": "^7.23.3",
+                "@babel/plugin-transform-reserved-words": "^7.23.3",
+                "@babel/plugin-transform-shorthand-properties": "^7.23.3",
+                "@babel/plugin-transform-spread": "^7.23.3",
+                "@babel/plugin-transform-sticky-regex": "^7.23.3",
+                "@babel/plugin-transform-template-literals": "^7.23.3",
+                "@babel/plugin-transform-typeof-symbol": "^7.23.3",
+                "@babel/plugin-transform-unicode-escapes": "^7.23.3",
+                "@babel/plugin-transform-unicode-property-regex": "^7.23.3",
+                "@babel/plugin-transform-unicode-regex": "^7.23.3",
+                "@babel/plugin-transform-unicode-sets-regex": "^7.23.3",
+                "@babel/preset-modules": "0.1.6-no-external-plugins",
+                "babel-plugin-polyfill-corejs2": "^0.4.6",
+                "babel-plugin-polyfill-corejs3": "^0.8.5",
+                "babel-plugin-polyfill-regenerator": "^0.5.3",
+                "core-js-compat": "^3.31.0",
+                "semver": "^6.3.1"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
+            }
+        },
+        "node_modules/@babel/preset-env/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
             "bin": {
-                "parser": "bin/babel-parser.js"
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/@babel/preset-modules": {
+            "version": "0.1.6-no-external-plugins",
+            "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+            "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.0.0",
+                "@babel/types": "^7.4.4",
+                "esutils": "^2.0.2"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+            }
+        },
+        "node_modules/@babel/preset-react": {
+            "version": "7.23.3",
+            "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz",
+            "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==",
+            "dependencies": {
+                "@babel/helper-plugin-utils": "^7.22.5",
+                "@babel/helper-validator-option": "^7.22.15",
+                "@babel/plugin-transform-react-display-name": "^7.23.3",
+                "@babel/plugin-transform-react-jsx": "^7.22.15",
+                "@babel/plugin-transform-react-jsx-development": "^7.22.5",
+                "@babel/plugin-transform-react-pure-annotations": "^7.23.3"
             },
             "engines": {
-                "node": ">=6.0.0"
+                "node": ">=6.9.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.0.0-0"
             }
         },
+        "node_modules/@babel/regjsgen": {
+            "version": "0.8.0",
+            "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz",
+            "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
+        },
         "node_modules/@babel/runtime": {
             "version": "7.23.5",
             "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
@@ -3215,9 +4812,9 @@
             }
         },
         "node_modules/@types/react": {
-            "version": "18.2.42",
-            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.42.tgz",
-            "integrity": "sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==",
+            "version": "18.2.43",
+            "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.43.tgz",
+            "integrity": "sha512-nvOV01ZdBdd/KW6FahSbcNplt2jCJfyWdTos61RYHV+FVv5L/g9AOX1bmbVcWcLFL8+KHQfh1zVIQrud6ihyQA==",
             "dependencies": {
                 "@types/prop-types": "*",
                 "@types/scheduler": "*",
@@ -4231,6 +5828,23 @@
             "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
             "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
         },
+        "node_modules/array-batcher/node_modules/camelcase": {
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/array-batcher/node_modules/debug": {
+            "version": "3.2.6",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+            "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+            "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+            "dependencies": {
+                "ms": "^2.1.1"
+            }
+        },
         "node_modules/array-batcher/node_modules/glob": {
             "version": "7.1.3",
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
@@ -4306,6 +5920,15 @@
                 "node": ">=6"
             }
         },
+        "node_modules/array-batcher/node_modules/yargs-parser": {
+            "version": "13.1.2",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+            "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+            "dependencies": {
+                "camelcase": "^5.0.0",
+                "decamelize": "^1.2.0"
+            }
+        },
         "node_modules/array-buffer-byte-length": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -4422,13 +6045,13 @@
             }
         },
         "node_modules/array.prototype.reduce": {
-            "version": "1.0.5",
-            "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz",
-            "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==",
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.6.tgz",
+            "integrity": "sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==",
             "dependencies": {
                 "call-bind": "^1.0.2",
-                "define-properties": "^1.1.4",
-                "es-abstract": "^1.20.4",
+                "define-properties": "^1.2.0",
+                "es-abstract": "^1.22.1",
                 "es-array-method-boxes-properly": "^1.0.0",
                 "is-string": "^1.0.7"
             },
@@ -4765,6 +6388,115 @@
                 "node": ">=4"
             }
         },
+        "node_modules/babel-loader": {
+            "version": "9.1.3",
+            "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz",
+            "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==",
+            "dependencies": {
+                "find-cache-dir": "^4.0.0",
+                "schema-utils": "^4.0.0"
+            },
+            "engines": {
+                "node": ">= 14.15.0"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.12.0",
+                "webpack": ">=5"
+            }
+        },
+        "node_modules/babel-loader/node_modules/ajv": {
+            "version": "8.12.0",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+            "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "json-schema-traverse": "^1.0.0",
+                "require-from-string": "^2.0.2",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
+        "node_modules/babel-loader/node_modules/ajv-keywords": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+            "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+            "dependencies": {
+                "fast-deep-equal": "^3.1.3"
+            },
+            "peerDependencies": {
+                "ajv": "^8.8.2"
+            }
+        },
+        "node_modules/babel-loader/node_modules/json-schema-traverse": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+            "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+        },
+        "node_modules/babel-loader/node_modules/schema-utils": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+            "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
+            "dependencies": {
+                "@types/json-schema": "^7.0.9",
+                "ajv": "^8.9.0",
+                "ajv-formats": "^2.1.1",
+                "ajv-keywords": "^5.1.0"
+            },
+            "engines": {
+                "node": ">= 12.13.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/webpack"
+            }
+        },
+        "node_modules/babel-plugin-polyfill-corejs2": {
+            "version": "0.4.6",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz",
+            "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==",
+            "dependencies": {
+                "@babel/compat-data": "^7.22.6",
+                "@babel/helper-define-polyfill-provider": "^0.4.3",
+                "semver": "^6.3.1"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+            }
+        },
+        "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
+            "version": "6.3.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+            "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/babel-plugin-polyfill-corejs3": {
+            "version": "0.8.6",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz",
+            "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==",
+            "dependencies": {
+                "@babel/helper-define-polyfill-provider": "^0.4.3",
+                "core-js-compat": "^3.33.1"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+            }
+        },
+        "node_modules/babel-plugin-polyfill-regenerator": {
+            "version": "0.5.3",
+            "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz",
+            "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==",
+            "dependencies": {
+                "@babel/helper-define-polyfill-provider": "^0.4.3"
+            },
+            "peerDependencies": {
+                "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+            }
+        },
         "node_modules/babel-runtime": {
             "version": "6.26.0",
             "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -8037,9 +9769,9 @@
             "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
         },
         "node_modules/browserslist": {
-            "version": "4.21.9",
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
-            "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
+            "version": "4.22.2",
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
+            "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -8055,10 +9787,10 @@
                 }
             ],
             "dependencies": {
-                "caniuse-lite": "^1.0.30001503",
-                "electron-to-chromium": "^1.4.431",
-                "node-releases": "^2.0.12",
-                "update-browserslist-db": "^1.0.11"
+                "caniuse-lite": "^1.0.30001565",
+                "electron-to-chromium": "^1.4.601",
+                "node-releases": "^2.0.14",
+                "update-browserslist-db": "^1.0.13"
             },
             "bin": {
                 "browserslist": "cli.js"
@@ -8268,11 +10000,11 @@
             }
         },
         "node_modules/camelcase": {
-            "version": "5.3.1",
-            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
-            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+            "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==",
             "engines": {
-                "node": ">=6"
+                "node": ">=4"
             }
         },
         "node_modules/camelize": {
@@ -8284,9 +10016,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001514",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz",
-            "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==",
+            "version": "1.0.30001566",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz",
+            "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -8479,18 +10211,6 @@
                 "node": ">=10"
             }
         },
-        "node_modules/chrome": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/chrome/-/chrome-0.1.0.tgz",
-            "integrity": "sha512-6KYl20U4Taj6YipylsWr2etUvp9AElJKfGNSBmyGTymYmancnOb041ZNadolEZi2nboLXH7jMSqUmm4kpuTzfg==",
-            "dependencies": {
-                "exeq": "^2.2.0",
-                "plist": "^1.1.0"
-            },
-            "bin": {
-                "chrome": "index.js"
-            }
-        },
         "node_modules/chrome-trace-event": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
@@ -8583,22 +10303,6 @@
                 "yargs-parser": "^7.0.0"
             }
         },
-        "node_modules/cliss/node_modules/camelcase": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-            "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
-            "engines": {
-                "node": ">=4"
-            }
-        },
-        "node_modules/cliss/node_modules/yargs-parser": {
-            "version": "7.0.0",
-            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
-            "integrity": "sha512-WhzC+xgstid9MbVUktco/bf+KJG+Uu6vMX0LN1sLJvwmbCQVxb4D8LzogobonKycNasCZLdOzTAk1SK7+K7swg==",
-            "dependencies": {
-                "camelcase": "^4.1.0"
-            }
-        },
         "node_modules/cliui": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -8793,6 +10497,11 @@
                 "node": ">= 12"
             }
         },
+        "node_modules/common-path-prefix": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
+            "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
+        },
         "node_modules/component-emitter": {
             "version": "1.3.1",
             "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@@ -9025,14 +10734,6 @@
                 "node": ">= 0.10"
             }
         },
-        "node_modules/cookie-session/node_modules/debug": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-            "dependencies": {
-                "ms": "^2.1.1"
-            }
-        },
         "node_modules/cookie-session/node_modules/safe-buffer": {
             "version": "5.2.1",
             "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -9087,6 +10788,18 @@
                 "url": "https://opencollective.com/core-js"
             }
         },
+        "node_modules/core-js-compat": {
+            "version": "3.34.0",
+            "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz",
+            "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==",
+            "dependencies": {
+                "browserslist": "^4.22.2"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/core-js"
+            }
+        },
         "node_modules/core-js-pure": {
             "version": "3.28.0",
             "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.28.0.tgz",
@@ -9439,9 +11152,9 @@
             }
         },
         "node_modules/csv-stringify": {
-            "version": "6.4.4",
-            "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.4.tgz",
-            "integrity": "sha512-NDshLupGa7gp4UG4sSNIqwYJqgSwvds0SvENntxoVoVvTzXcrHvd5gG2MWpbRpSNvk59dlmIe1IwNvSxN4IVmg=="
+            "version": "6.4.5",
+            "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz",
+            "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ=="
         },
         "node_modules/custom-event": {
             "version": "1.0.1",
@@ -9912,10 +11625,9 @@
             }
         },
         "node_modules/debug": {
-            "version": "3.2.6",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
-            "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
-            "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+            "version": "3.2.7",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
             "dependencies": {
                 "ms": "^2.1.1"
             }
@@ -9923,7 +11635,7 @@
         "node_modules/decamelize": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-            "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+            "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
             "engines": {
                 "node": ">=0.10.0"
             }
@@ -10093,18 +11805,6 @@
                 "node": ">=10.17.0"
             }
         },
-        "node_modules/default-gateway/node_modules/is-stream": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
-            "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/default-gateway/node_modules/npm-run-path": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -10756,9 +12456,9 @@
             "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
         },
         "node_modules/electron-to-chromium": {
-            "version": "1.4.454",
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz",
-            "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ=="
+            "version": "1.4.609",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
+            "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw=="
         },
         "node_modules/emoji-regex": {
             "version": "7.0.3",
@@ -11957,15 +13657,6 @@
                 "resolve": "^1.22.4"
             }
         },
-        "node_modules/eslint-import-resolver-node/node_modules/debug": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-            "dev": true,
-            "dependencies": {
-                "ms": "^2.1.1"
-            }
-        },
         "node_modules/eslint-module-utils": {
             "version": "2.8.0",
             "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
@@ -11983,15 +13674,6 @@
                 }
             }
         },
-        "node_modules/eslint-module-utils/node_modules/debug": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-            "dev": true,
-            "dependencies": {
-                "ms": "^2.1.1"
-            }
-        },
         "node_modules/eslint-plugin-babel": {
             "version": "5.3.1",
             "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz",
@@ -12081,15 +13763,6 @@
                 "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
             }
         },
-        "node_modules/eslint-plugin-import/node_modules/debug": {
-            "version": "3.2.7",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-            "dev": true,
-            "dependencies": {
-                "ms": "^2.1.1"
-            }
-        },
         "node_modules/eslint-plugin-import/node_modules/doctrine": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -12840,18 +14513,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/exeq": {
-            "version": "2.4.0",
-            "resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz",
-            "integrity": "sha1-Td8qaEZIxCeteZNJzzO9dTWPiEo=",
-            "dependencies": {
-                "bluebird": "^3.0.3",
-                "native-or-bluebird": "^1.2.0"
-            },
-            "engines": {
-                "node": ">= 0.10.0"
-            }
-        },
         "node_modules/exif": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz",
@@ -13491,6 +15152,111 @@
                 "traverse-chain": "~0.1.0"
             }
         },
+        "node_modules/find-cache-dir": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz",
+            "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==",
+            "dependencies": {
+                "common-path-prefix": "^3.0.0",
+                "pkg-dir": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=14.16"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/find-up": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
+            "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
+            "dependencies": {
+                "locate-path": "^7.1.0",
+                "path-exists": "^5.0.0"
+            },
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/locate-path": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
+            "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
+            "dependencies": {
+                "p-locate": "^6.0.0"
+            },
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/p-limit": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+            "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+            "dependencies": {
+                "yocto-queue": "^1.0.0"
+            },
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/p-locate": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
+            "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
+            "dependencies": {
+                "p-limit": "^4.0.0"
+            },
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/path-exists": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
+            "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
+            "engines": {
+                "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/pkg-dir": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
+            "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
+            "dependencies": {
+                "find-up": "^6.3.0"
+            },
+            "engines": {
+                "node": ">=14.16"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/find-cache-dir/node_modules/yocto-queue": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+            "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+            "engines": {
+                "node": ">=12.20"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/find-in-files": {
             "version": "0.5.0",
             "resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz",
@@ -14140,9 +15906,9 @@
             }
         },
         "node_modules/function-plot": {
-            "version": "1.23.3",
-            "resolved": "https://registry.npmjs.org/function-plot/-/function-plot-1.23.3.tgz",
-            "integrity": "sha512-YvV517ZNrc93SBlLux3LfNZUEil7DiZGHerKGk5tU/iqIzFYedyw3u7ZeAcrll3qzhKTs84M8XxUiESE7Mlzdw==",
+            "version": "1.23.4",
+            "resolved": "https://registry.npmjs.org/function-plot/-/function-plot-1.23.4.tgz",
+            "integrity": "sha512-CMu6k8MDPpOOvnFBRhahtqm0UKzxhxxFYkp1p5Xd06iTDenhpvVh1s0/uR6GTymJX8Hdgwr4iWsEUWXOhgtJUQ==",
             "dependencies": {
                 "built-in-math-eval": "^0.3.0",
                 "clamp": "^1.0.1",
@@ -14154,6 +15920,7 @@
                 "d3-selection": "^3.0.0",
                 "d3-shape": "^3.1.0",
                 "d3-zoom": "^3.0.0",
+                "events": "^3.3.0",
                 "interval-arithmetic-eval": "^0.5.1"
             }
         },
@@ -14305,32 +16072,48 @@
                 "node": ">= 14"
             }
         },
-        "node_modules/gaxios/node_modules/is-stream": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
-            "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/gaxios/node_modules/ms": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
             "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
         "node_modules/gcp-metadata": {
-            "version": "6.1.0",
-            "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
-            "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
+            "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
+            "optional": true,
+            "peer": true,
             "dependencies": {
-                "gaxios": "^6.0.0",
+                "gaxios": "^5.0.0",
                 "json-bigint": "^1.0.0"
             },
             "engines": {
-                "node": ">=14"
+                "node": ">=12"
+            }
+        },
+        "node_modules/gcp-metadata/node_modules/gaxios": {
+            "version": "5.1.3",
+            "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
+            "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
+            "optional": true,
+            "peer": true,
+            "dependencies": {
+                "extend": "^3.0.2",
+                "https-proxy-agent": "^5.0.0",
+                "is-stream": "^2.0.0",
+                "node-fetch": "^2.6.9"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/gensync": {
+            "version": "1.0.0-beta.2",
+            "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+            "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+            "peer": true,
+            "engines": {
+                "node": ">=6.9.0"
             }
         },
         "node_modules/get-caller-file": {
@@ -14550,6 +16333,18 @@
                 "node": ">=14"
             }
         },
+        "node_modules/google-auth-library/node_modules/gcp-metadata": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
+            "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
+            "dependencies": {
+                "gaxios": "^6.0.0",
+                "json-bigint": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
         "node_modules/googleapis": {
             "version": "129.0.0",
             "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-129.0.0.tgz",
@@ -15676,14 +17471,6 @@
                 "url": "https://www.buymeacoffee.com/2tmRKi9"
             }
         },
-        "node_modules/infobox-parser/node_modules/camelcase": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-            "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
-            "engines": {
-                "node": ">=4"
-            }
-        },
         "node_modules/inherits": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -16483,6 +18270,17 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/is-stream": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+            "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
         "node_modules/is-string": {
             "version": "1.0.7",
             "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@@ -16718,6 +18516,31 @@
                 "regenerator-runtime": "^0.13.3"
             }
         },
+        "node_modules/jpeg-autorotate": {
+            "version": "9.0.0",
+            "resolved": "https://registry.npmjs.org/jpeg-autorotate/-/jpeg-autorotate-9.0.0.tgz",
+            "integrity": "sha512-bMzp1iIOpqF2dWiMwb3Mc9MfexYuiBmznNIeVtnedqFrfVql3Ueptg7Cefz6ZJuJais8sb+mBGQD/hw3Ud8eRA==",
+            "dependencies": {
+                "glob": "^7.2.0",
+                "jpeg-js": "^0.4.3",
+                "piexifjs": "^1.0.6",
+                "yargs-parser": "^20.2.9"
+            },
+            "bin": {
+                "jpeg-autorotate": "src/cli.js"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/jpeg-autorotate/node_modules/yargs-parser": {
+            "version": "20.2.9",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+            "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+            "engines": {
+                "node": ">=10"
+            }
+        },
         "node_modules/jpeg-js": {
             "version": "0.4.4",
             "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
@@ -17341,6 +19164,11 @@
             "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
             "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
         },
+        "node_modules/lodash.debounce": {
+            "version": "4.0.8",
+            "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+            "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+        },
         "node_modules/lodash.get": {
             "version": "4.4.2",
             "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -18665,33 +20493,68 @@
             "integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g=="
         },
         "node_modules/mobx": {
-            "version": "5.15.7",
-            "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
-            "integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
+            "version": "6.12.0",
+            "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.0.tgz",
+            "integrity": "sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ==",
             "funding": {
                 "type": "opencollective",
                 "url": "https://opencollective.com/mobx"
             }
         },
         "node_modules/mobx-react": {
-            "version": "5.4.4",
-            "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.4.tgz",
-            "integrity": "sha512-2mTzpyEjVB/RGk2i6KbcmP4HWcAUFox5ZRCrGvSyz49w20I4C4qql63grPpYrS9E9GKwgydBHQlA4y665LuRCQ==",
+            "version": "9.1.0",
+            "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.0.tgz",
+            "integrity": "sha512-DeDRTYw4AlgHw8xEXtiZdKKEnp+c5/jeUgTbTQXEqnAzfkrgYRWP3p3Nv3Whc2CEcM/mDycbDWGjxKokQdlffg==",
+            "dependencies": {
+                "mobx-react-lite": "^4.0.4"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mobx"
+            },
+            "peerDependencies": {
+                "mobx": "^6.9.0",
+                "react": "^16.8.0 || ^17 || ^18"
+            },
+            "peerDependenciesMeta": {
+                "react-dom": {
+                    "optional": true
+                },
+                "react-native": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/mobx-react-lite": {
+            "version": "4.0.5",
+            "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.5.tgz",
+            "integrity": "sha512-StfB2wxE8imKj1f6T8WWPf4lVMx3cYH9Iy60bbKXEs21+HQ4tvvfIBZfSmMXgQAefi8xYEwQIz4GN9s0d2h7dg==",
             "dependencies": {
-                "hoist-non-react-statics": "^3.0.0",
-                "react-lifecycles-compat": "^3.0.2"
+                "use-sync-external-store": "^1.2.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/mobx"
             },
             "peerDependencies": {
-                "mobx": "^4.0.0 || ^5.0.0",
-                "react": "^0.13.0 || ^0.14.0 || ^15.0.0 || ^16.0.0"
+                "mobx": "^6.9.0",
+                "react": "^16.8.0 || ^17 || ^18"
+            },
+            "peerDependenciesMeta": {
+                "react-dom": {
+                    "optional": true
+                },
+                "react-native": {
+                    "optional": true
+                }
             }
         },
         "node_modules/mobx-utils": {
-            "version": "5.6.2",
-            "resolved": "https://registry.npmjs.org/mobx-utils/-/mobx-utils-5.6.2.tgz",
-            "integrity": "sha512-a/WlXyGkp6F12b01sTarENpxbmlRgPHFyR1Xv2bsSjQBm5dcOtd16ONb40/vOqck8L99NHpI+C9MXQ+SZ8f+yw==",
+            "version": "6.0.8",
+            "resolved": "https://registry.npmjs.org/mobx-utils/-/mobx-utils-6.0.8.tgz",
+            "integrity": "sha512-fPNt0vJnHwbQx9MojJFEnJLfM3EMGTtpy4/qOOW6xueh1mPofMajrbYAUvByMYAvCJnpy1A5L0t+ZVB5niKO4g==",
             "peerDependencies": {
-                "mobx": "^4.13.1 || ^5.13.1"
+                "mobx": "^6.0.0"
             }
         },
         "node_modules/mocha": {
@@ -19609,12 +21472,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/native-or-bluebird": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz",
-            "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=",
-            "deprecated": "'native-or-bluebird' is deprecated. Please use 'any-promise' instead."
-        },
         "node_modules/natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -19745,9 +21602,9 @@
             }
         },
         "node_modules/node-releases": {
-            "version": "2.0.13",
-            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
-            "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
+            "version": "2.0.14",
+            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+            "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
         },
         "node_modules/node-stream-zip": {
             "version": "1.15.0",
@@ -22823,14 +24680,15 @@
             }
         },
         "node_modules/object.getownpropertydescriptors": {
-            "version": "2.1.5",
-            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz",
-            "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==",
+            "version": "2.1.7",
+            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.7.tgz",
+            "integrity": "sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g==",
             "dependencies": {
-                "array.prototype.reduce": "^1.0.5",
+                "array.prototype.reduce": "^1.0.6",
                 "call-bind": "^1.0.2",
-                "define-properties": "^1.1.4",
-                "es-abstract": "^1.20.4"
+                "define-properties": "^1.2.0",
+                "es-abstract": "^1.22.1",
+                "safe-array-concat": "^1.0.0"
             },
             "engines": {
                 "node": ">= 0.8"
@@ -23553,6 +25411,11 @@
                 "url": "https://github.com/sponsors/jonschlinkert"
             }
         },
+        "node_modules/piexifjs": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
+            "integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
+        },
         "node_modules/pipe-functions": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/pipe-functions/-/pipe-functions-1.3.0.tgz",
@@ -23644,25 +25507,6 @@
                 "semver-compare": "^1.0.0"
             }
         },
-        "node_modules/plist": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz",
-            "integrity": "sha512-dL9Xc2Aj3YyBnwvCNuHmFl2LWvQacm/HEAsoVwLiuu0POboMChETt5wexpU1P6F6MnibIucXlVsMFFgNUT2IyA==",
-            "dependencies": {
-                "base64-js": "0.0.8",
-                "util-deprecate": "1.0.2",
-                "xmlbuilder": "4.0.0",
-                "xmldom": "0.1.x"
-            }
-        },
-        "node_modules/plist/node_modules/base64-js": {
-            "version": "0.0.8",
-            "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
-            "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=",
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/pngjs": {
             "version": "3.4.0",
             "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
@@ -23805,9 +25649,9 @@
             }
         },
         "node_modules/prettier": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
-            "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
+            "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
             "dev": true,
             "bin": {
                 "prettier": "bin/prettier.cjs"
@@ -23899,6 +25743,13 @@
                 "url": "https://github.com/sponsors/wooorm"
             }
         },
+        "node_modules/prosemirror": {
+            "version": "0.11.1",
+            "resolved": "https://registry.npmjs.org/prosemirror/-/prosemirror-0.11.1.tgz",
+            "integrity": "sha512-IZ35mmexuo3DNkMXs2A3bAm1jdlAJRVPor/bX1AWT+TdAR9fv6igUjRUFtQDCRUKwxH7ufwL1p6Um/MdgcbVIw==",
+            "deprecated": "This library has been split up into separate modules. This is the old monolithic package, which is no longer maintained. Use prosemirror-view, prosemirror-model, etc instead.",
+            "peer": true
+        },
         "node_modules/prosemirror-commands": {
             "version": "1.5.2",
             "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz",
@@ -24365,15 +26216,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/react-audio-waveform": {
-            "version": "0.0.5",
-            "resolved": "https://registry.npmjs.org/react-audio-waveform/-/react-audio-waveform-0.0.5.tgz",
-            "integrity": "sha512-4dJwhl+LQRuywzmmjuhWT5GueT3Ai2ZhB+loVxg0sRdhplyDp96xAu10/V/+J25Cl7YqNtl2DWSxvSoI/i6l6w==",
-            "peerDependencies": {
-                "react": "^15.5.4",
-                "react-dom": "^15.4.1"
-            }
-        },
         "node_modules/react-autosuggest": {
             "version": "10.1.0",
             "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.1.0.tgz",
@@ -24675,17 +26517,6 @@
                 "react": ">= 16.3"
             }
         },
-        "node_modules/react-reveal": {
-            "version": "1.2.2",
-            "resolved": "https://registry.npmjs.org/react-reveal/-/react-reveal-1.2.2.tgz",
-            "integrity": "sha512-JCv3fAoU6Z+Lcd8U48bwzm4pMZ79qsedSXYwpwt6lJNtj/v5nKJYZZbw3yhaQPPgYePo3Y0NOCoYOq/jcsisuw==",
-            "dependencies": {
-                "prop-types": "^15.5.10"
-            },
-            "peerDependencies": {
-                "react": "^15.3.0 || ^16.0.0"
-            }
-        },
         "node_modules/react-select": {
             "version": "5.8.0",
             "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz",
@@ -24956,11 +26787,35 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/regenerate": {
+            "version": "1.4.2",
+            "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+            "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="
+        },
+        "node_modules/regenerate-unicode-properties": {
+            "version": "10.1.1",
+            "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz",
+            "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==",
+            "dependencies": {
+                "regenerate": "^1.4.2"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/regenerator-runtime": {
             "version": "0.13.11",
             "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
             "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
         },
+        "node_modules/regenerator-transform": {
+            "version": "0.15.2",
+            "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
+            "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
+            "dependencies": {
+                "@babel/runtime": "^7.8.4"
+            }
+        },
         "node_modules/regex-not": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -25024,6 +26879,41 @@
                 "url": "https://github.com/sponsors/mysticatea"
             }
         },
+        "node_modules/regexpu-core": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz",
+            "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==",
+            "dependencies": {
+                "@babel/regjsgen": "^0.8.0",
+                "regenerate": "^1.4.2",
+                "regenerate-unicode-properties": "^10.1.0",
+                "regjsparser": "^0.9.1",
+                "unicode-match-property-ecmascript": "^2.0.0",
+                "unicode-match-property-value-ecmascript": "^2.1.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/regjsparser": {
+            "version": "0.9.1",
+            "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz",
+            "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==",
+            "dependencies": {
+                "jsesc": "~0.5.0"
+            },
+            "bin": {
+                "regjsparser": "bin/parser"
+            }
+        },
+        "node_modules/regjsparser/node_modules/jsesc": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+            "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
+            "bin": {
+                "jsesc": "bin/jsesc"
+            }
+        },
         "node_modules/rehype-raw": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
@@ -25629,18 +27519,6 @@
                 "node": ">=10.17.0"
             }
         },
-        "node_modules/run-applescript/node_modules/is-stream": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
-            "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-            "dev": true,
-            "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/run-applescript/node_modules/npm-run-path": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -28159,7 +30037,6 @@
             "version": "4.9.5",
             "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
             "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
-            "dev": true,
             "bin": {
                 "tsc": "bin/tsc",
                 "tsserver": "bin/tsserver"
@@ -28174,9 +30051,9 @@
             "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ=="
         },
         "node_modules/typescript-language-server": {
-            "version": "4.1.3",
-            "resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.1.3.tgz",
-            "integrity": "sha512-miJcwsS5ZGuYlix9mbGZDF96ynzgxAZbCeQO2MpgaVahmnRjoZ3dI6hnnsvBbdTrpHAAEqdigOnVezoOzcXSag==",
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.2.0.tgz",
+            "integrity": "sha512-1yKDqKeWLTQkN4mN+CT84aBr7ckp6sNVb8DZg+eXl0TDl14edn6Yh1wPqPA1rQ4AGVJc02fYbXTFsklaVYy4Uw==",
             "bin": {
                 "typescript-language-server": "lib/cli.mjs"
             },
@@ -28229,6 +30106,42 @@
             "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
             "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
         },
+        "node_modules/unicode-canonical-property-names-ecmascript": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
+            "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/unicode-match-property-ecmascript": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+            "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+            "dependencies": {
+                "unicode-canonical-property-names-ecmascript": "^2.0.0",
+                "unicode-property-aliases-ecmascript": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/unicode-match-property-value-ecmascript": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz",
+            "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/unicode-property-aliases-ecmascript": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
+            "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/unicode-trie": {
             "version": "0.3.1",
             "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
@@ -28453,9 +30366,9 @@
             }
         },
         "node_modules/update-browserslist-db": {
-            "version": "1.0.11",
-            "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
-            "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
+            "version": "1.0.13",
+            "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+            "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -28608,6 +30521,14 @@
                 }
             }
         },
+        "node_modules/use-sync-external-store": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+            "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+            }
+        },
         "node_modules/utif2": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
@@ -29454,9 +31375,9 @@
             }
         },
         "node_modules/which-module": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
-            "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+            "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
         },
         "node_modules/which-typed-array": {
             "version": "1.1.13",
@@ -29785,37 +31706,12 @@
                 "node": ">=4.0"
             }
         },
-        "node_modules/xmlbuilder": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz",
-            "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=",
-            "dependencies": {
-                "lodash": "^3.5.0"
-            },
-            "engines": {
-                "node": ">=0.8.0"
-            }
-        },
-        "node_modules/xmlbuilder/node_modules/lodash": {
-            "version": "3.10.1",
-            "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-            "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ=="
-        },
         "node_modules/xmlchars": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
             "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
             "dev": true
         },
-        "node_modules/xmldom": {
-            "version": "0.1.31",
-            "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
-            "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==",
-            "deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0",
-            "engines": {
-                "node": ">=0.1"
-            }
-        },
         "node_modules/xmlhttprequest-ssl": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
@@ -29881,12 +31777,11 @@
             }
         },
         "node_modules/yargs-parser": {
-            "version": "13.1.2",
-            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
-            "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
+            "integrity": "sha512-WhzC+xgstid9MbVUktco/bf+KJG+Uu6vMX0LN1sLJvwmbCQVxb4D8LzogobonKycNasCZLdOzTAk1SK7+K7swg==",
             "dependencies": {
-                "camelcase": "^5.0.0",
-                "decamelize": "^1.2.0"
+                "camelcase": "^4.1.0"
             }
         },
         "node_modules/yargs-unparser": {
@@ -29910,6 +31805,14 @@
                 "node": ">=6"
             }
         },
+        "node_modules/yargs/node_modules/camelcase": {
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/yargs/node_modules/string-width": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
@@ -29934,6 +31837,15 @@
                 "node": ">=6"
             }
         },
+        "node_modules/yargs/node_modules/yargs-parser": {
+            "version": "13.1.2",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+            "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+            "dependencies": {
+                "camelcase": "^5.0.0",
+                "decamelize": "^1.2.0"
+            }
+        },
         "node_modules/yn": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
diff --git a/package.json b/package.json
index 151e45124..42b3c1ce2 100644
--- a/package.json
+++ b/package.json
@@ -95,6 +95,8 @@
     },
     "dependencies": {
         "@azure/storage-blob": "^12.17.0",
+        "@babel/preset-env": "^7.23.5",
+        "@babel/preset-react": "^7.23.3",
         "@emotion/react": "^11.11.1",
         "@emotion/styled": "^11.11.0",
         "@ffmpeg/core": "0.12.4",
@@ -127,6 +129,7 @@
         "async": "^3.2.5",
         "axios": "^1.6.2",
         "babel": "^6.23.0",
+        "babel-loader": "^9.1.3",
         "bcrypt-nodejs": "0.0.3",
         "bezier-curve": "^1.0.0",
         "bezier-js": "^6.1.4",
@@ -141,7 +144,6 @@
         "canvas": "^2.11.2",
         "chart.js": "^4.4.0",
         "child_process": "^1.0.2",
-        "chrome": "^0.1.0",
         "class-transformer": "^0.5.1",
         "color": "^4.2.3",
         "colors": "^1.4.0",
@@ -191,6 +193,7 @@
         "image-size": "^1.0.2",
         "image-size-stream": "^1.1.0",
         "jimp": "^0.22.10",
+        "jpeg-autorotate": "^9.0.0",
         "jquery": "^3.7.1",
         "js-datepicker": "^5.18.2",
         "jsonschema": "^1.4.1",
@@ -200,9 +203,9 @@
         "md5-file": "^5.0.0",
         "memorystream": "^0.3.1",
         "mobile-detect": "^1.4.5",
-        "mobx": "^5.15.7",
-        "mobx-react": "^5.4.4",
-        "mobx-utils": "^5.6.2",
+        "mobx": "^6.12.0",
+        "mobx-react": "^9.1.0",
+        "mobx-utils": "^6.0.8",
         "mongodb": "^6.3.0",
         "mongoose": "^8.0.2",
         "node-stream-zip": "^1.15.0",
@@ -237,7 +240,6 @@
         "raw-loader": "^4.0.2",
         "rc-switch": "^4.1.0",
         "react": "^18.2.0",
-        "react-audio-waveform": "0.0.5",
         "react-autosuggest": "^10.1.0",
         "react-color": "^2.19.3",
         "react-compound-slider": "^3.4.0",
@@ -250,7 +252,6 @@
         "react-markdown": "^9.0.1",
         "react-measure": "^2.5.2",
         "react-resizable": "^3.0.5",
-        "react-reveal": "^1.2.2",
         "react-select": "^5.8.0",
         "readline": "^1.3.0",
         "recharts": "^2.10.3",
diff --git a/src/.babelrc b/src/.babelrc
new file mode 100644
index 000000000..86ef21087
--- /dev/null
+++ b/src/.babelrc
@@ -0,0 +1,3 @@
+{
+    "presets": ["@babel/preset-env", "@babel/preset-react"]
+}
diff --git a/src/Utils.ts b/src/Utils.ts
index 26514622c..d54760100 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,6 +7,7 @@ import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
 import * as Color from 'color';
+import { action, makeObservable } from 'mobx';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -439,6 +440,12 @@ export namespace Utils {
         socket.on(message, (room: any) => handler(socket, room));
     }
 }
+export function copyProps(thing: { _prevProps: any; props: any; _props: any }) {
+    Object.keys(thing._prevProps).forEach(action(pkey => 
+        (thing._prevProps as any)[pkey] !== (thing.props as any)[pkey] && 
+            ((thing._props as any)[pkey] = (thing.props as any)[pkey]))); // prettier-ignore
+    thing._prevProps = thing.props;
+}
 
 export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
     const omit: any = { ...obj };
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index df92fe50e..6217cf04b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -57,7 +57,7 @@ export namespace DocServer {
                 Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
             }
         });
-        LinkManager.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+        LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
         const filtered = Array.from(references);
 
         const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 87010f770..427b11751 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -32,7 +32,6 @@ export enum DocumentType {
     IMPORT = 'import',
     PRES = 'presentation',
     PRESELEMENT = 'preselement',
-    COLOR = 'color',
     YOUTUBE = 'youtube',
     COMPARISON = 'comparison',
     GROUP = 'group',
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4ec100728..37196187b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,7 +32,6 @@ import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
 import { AudioBox, media_state } from '../views/nodes/AudioBox';
-import { ColorBox } from '../views/nodes/ColorBox';
 import { ComparisonBox } from '../views/nodes/ComparisonBox';
 import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
 import { EquationBox } from '../views/nodes/EquationBox';
@@ -495,13 +494,6 @@ export namespace Docs {
                     options: { _width: 400 },
                 },
             ],
-            [
-                DocumentType.COLOR,
-                {
-                    layout: { view: ColorBox, dataField: defaultDataKey },
-                    options: { _nativeWidth: 220, _nativeHeight: 300 },
-                },
-            ],
             [
                 DocumentType.IMG,
                 {
@@ -1018,9 +1010,6 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List<Doc>([]), options);
         }
 
-        export function ColorDocument(options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options);
-        }
         export function LoadingDocument(file: File | string, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, '');
         }
@@ -1638,7 +1627,7 @@ export namespace DocUtils {
             options = { ...options, _width: 400, _height: 512, title: path };
         }
 
-        return ctor ? ctor(path, options, overwriteDoc) : undefined;
+        return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined;
     }
 
     export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ad5a1654d..7f82ff70a 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableSet, observe } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
@@ -22,8 +22,8 @@ const { Howl } = require('howler');
 export class DocumentManager {
     //global holds all of the nodes (regardless of which collection they're in)
     @observable _documentViews = new Set<DocumentView>();
-    @observable public LinkAnchorBoxViews: DocumentView[] = [];
-    @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
+    @observable public LinkAnchorBoxViews: DocumentView[] = observable([]);
+    @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = observable([]);
     @computed public get DocumentViews() {
         return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
     }
@@ -38,11 +38,12 @@ export class DocumentManager {
     public static get Instance(): DocumentManager {
         return this._instance || (this._instance = new this());
     }
+    @observable public CurrentlyLoading: Doc[] = observable([]); // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
 
     //private constructor so no other class can create a nodemanager
     private constructor() {
-        if (!LoadingBox.CurrentlyLoading) LoadingBox.CurrentlyLoading = [];
-        observe(LoadingBox.CurrentlyLoading, change => {
+        makeObservable(this);
+        observe(this.CurrentlyLoading, change => {
             // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become
             switch (change.type as any) {
                 case 'update':
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 30e448797..55aac5eb0 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -21,7 +21,6 @@ import { DocumentManager } from '../DocumentManager';
 import './DirectoryImportBox.scss';
 import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
 import * as React from 'react';
-import * as _ from 'lodash';
 
 const unsupported = ['text/html', 'text/plain'];
 
@@ -157,7 +156,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset,
         };
-        const parent = this.props.DocumentView?.().props.docViewPath().lastElement();
+        const parent = this.props.DocumentView?.()._props.docViewPath().lastElement();
         if (parent?.Document.type === DocumentType.COL) {
             let importContainer: Doc;
             if (docs.length < 50) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3e98ea379..8346949ba 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { action, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
 import { DirectLinks } from '../../fields/DocSymbols';
@@ -22,7 +22,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
  */
 export class LinkManager {
     @observable static _instance: LinkManager;
-    @observable static userLinkDBs: Doc[] = [];
+    @observable userLinkDBs: Doc[] = observable([]);
     @observable public static currentLink: Opt<Doc>;
     @observable public static currentLinkAnchor: Opt<Doc>;
     public static get Instance() {
@@ -32,21 +32,21 @@ export class LinkManager {
     public static Links(doc: Doc | undefined) {
         return doc ? LinkManager.Instance.getAllRelatedLinks(doc) : [];
     }
-    public static addLinkDB = async (linkDb: any) => {
+    public addLinkDB = async (linkDb: any) => {
         await Promise.all(
             ((await DocListCastAsync(linkDb.data)) ?? []).map(link =>
                 // makes sure link anchors are loaded to avoid incremental updates to computedFns in LinkManager
                 [PromiseValue(link?.link_anchor_1), PromiseValue(link?.link_anchor_2)]
             )
         );
-        LinkManager.userLinkDBs.push(linkDb);
+        this.userLinkDBs.push(linkDb);
     };
     public static AutoKeywords = 'keywords:Usages';
     static _links: Doc[] = [];
     constructor() {
+        makeObservable(this);
         LinkManager._instance = this;
         this.createlink_relationshipLists();
-        LinkManager.userLinkDBs = [];
         // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
         // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
         // Then add the link to the anchor protos.
@@ -124,7 +124,7 @@ export class LinkManager {
             }
         };
         observe(
-            LinkManager.userLinkDBs,
+            this.userLinkDBs,
             change => {
                 switch (change.type as any) {
                     case 'splice':
@@ -136,7 +136,7 @@ export class LinkManager {
             true
         );
         runInAction(() => (FieldLoader.ServerLoadStatus.message = 'links'));
-        LinkManager.addLinkDB(Doc.LinkDBDoc());
+        this.addLinkDB(Doc.LinkDBDoc());
     }
 
     public createlink_relationshipLists = () => {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index c8940194c..495af6abd 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -26,7 +26,7 @@ export class RTFMarkup extends React.Component<{}> {
         RTFMarkup.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined;
+    @observable _stats: { [key: string]: any } | undefined = undefined;
 
     /**
      * @returns the main interface of the SharingManager.
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 25f158f40..d36a360a1 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { Doc, Opt } from '../../fields/Doc';
 import { DocViews } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
@@ -12,9 +12,13 @@ import { UndoManager } from './UndoManager';
 
 export namespace SelectionManager {
     class Manager {
-        @observable SelectedViews: DocumentView[] = [];
+        @observable SelectedViews: DocumentView[] = observable([]);
         @observable IsDragging: boolean = false;
-        @observable SelectedSchemaDocument: Doc | undefined;
+        @observable SelectedSchemaDocument: Doc | undefined = undefined;
+
+        constructor() {
+            makeObservable(this);
+        }
 
         @action
         SelectSchemaViewDoc(doc: Opt<Doc>) {
@@ -26,7 +30,7 @@ export namespace SelectionManager {
                 if (!extendSelection) this.DeselectAll();
                 manager.SelectedViews.push(docView);
                 docView.SELECTED = true;
-                docView.props.whenChildContentsActiveChanged(true);
+                docView._props.whenChildContentsActiveChanged(true);
             }
         }
         @action
@@ -34,7 +38,7 @@ export namespace SelectionManager {
             if (docView && manager.SelectedViews.includes(docView)) {
                 docView.SELECTED = false;
                 manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
-                docView.props.whenChildContentsActiveChanged(false);
+                docView._props.whenChildContentsActiveChanged(false);
             }
         }
         @action
@@ -44,7 +48,7 @@ export namespace SelectionManager {
             manager.SelectedSchemaDocument = undefined;
             manager.SelectedViews.forEach(dv => {
                 dv.SELECTED = false;
-                dv.props.whenChildContentsActiveChanged(false);
+                dv._props.whenChildContentsActiveChanged(false);
             });
             manager.SelectedViews.length = 0;
         }
@@ -87,7 +91,7 @@ export namespace SelectionManager {
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
     if (Doc.noviceMode && expertMode) return false;
     if (type === 'tab') {
-        return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
+        return SelectionManager.Views().lastElement()?._props.renderDepth === 0;
     }
     let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
     return selected?.type === type || selected?.type_collection === type || !type;
@@ -115,7 +119,7 @@ ScriptingGlobals.add(function redo() {
 });
 ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
     const docs = SelectionManager.Views()
-        .map(dv => dv.props.Document)
+        .map(dv => dv.Document)
         .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 08dbaac5d..7d5e0458d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -36,7 +36,7 @@ export class ServerStats extends React.Component<{}> {
         ServerStats.Instance = this;
     }
 
-    @observable _stats: { [key: string]: any } | undefined;
+    @observable _stats: { [key: string]: any } | undefined = undefined;
 
     /**
      * @returns the main interface of the SharingManager.
@@ -56,9 +56,7 @@ export class ServerStats extends React.Component<{}> {
 
                     <br />
                     <span>Active users:{this._stats?.socketMap.length}</span>
-                    {this._stats?.socketMap.map((user: any) => (
-                        <p>{user.username}</p>
-                    ))}
+                    {this._stats?.socketMap.map((user: any) => <p>{user.username}</p>)}
                 </div>
             </div>
         );
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 39c471970..ccf6fb820 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { BsGoogle } from 'react-icons/bs';
@@ -45,11 +45,12 @@ export class SettingsManager extends React.Component<{}> {
     @observable private new_confirm = '';
     @observable activeTab = 'Accounts';
 
-    @observable public static propertiesWidth: number = 0;
-    @observable public static headerBarHeight: number = 0;
+    @observable public propertiesWidth: number = 0;
+    @observable public headerBarHeight: number = 0;
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         SettingsManager.Instance = this;
         this.matchSystem();
         window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
@@ -116,7 +117,6 @@ export class SettingsManager extends React.Component<{}> {
     });
 
     @undoBatch
-    @action
     changeColorScheme = action((scheme: string) => {
         Doc.UserDoc().userTheme = scheme;
         switch (scheme) {
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 403f4e090..a46c6363e 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -67,8 +67,8 @@ export class SharingManager extends React.Component<{}> {
     public static Instance: SharingManager;
     @observable private isOpen = false; // whether the SharingManager modal is open or not
     @observable public users: ValidatedUser[] = []; // the list of users with sharing docs
-    @observable private targetDoc: Doc | undefined; // the document being shared
-    @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared
+    @observable private targetDoc: Doc | undefined = undefined; // the document being shared
+    @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
     // @observable private copied = false;
     @observable private dialogueBoxOpacity = 1; // for the modal
     @observable private overlayOpacity = 0.4; // for the modal
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index fce43eef6..715eb021f 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -6,7 +6,7 @@ export namespace SnappingManager {
         @observable ShiftKey = false;
         @observable CtrlKey = false;
         @observable IsDragging: boolean = false;
-        @observable IsResizing: Doc | undefined;
+        @observable IsResizing: Doc | undefined = undefined;
         @observable CanEmbed: boolean = false;
         @observable public horizSnapLines: number[] = [];
         @observable public vertSnapLines: number[] = [];
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index 412b8ba6e..f89624941 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,9 +1,8 @@
+import { action, makeObservable, observable, runInAction } from 'mobx';
 import * as React from 'react';
-import { observable, action, runInAction } from 'mobx';
-import './AntimodeMenu.scss';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
 import { SettingsManager } from '../util/SettingsManager';
+import { copyProps } from '../../Utils';
+import './AntimodeMenu.scss';
 export interface AntimodeMenuProps {}
 
 /**
@@ -16,6 +15,18 @@ export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Co
     protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     protected _dragging: boolean = false;
 
+    _prevProps: React.PropsWithChildren<T>;
+    @observable _props: React.PropsWithChildren<T>;
+    constructor(props: React.PropsWithChildren<T>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable protected _top: number = -300;
     @observable protected _left: number = -300;
     @observable protected _opacity: number = 0;
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss
deleted file mode 100644
index 6cbd1759a..000000000
--- a/src/client/views/AudioWaveform.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.audioWaveform  {
-    position: relative;
-    width: 100%;
-    height: 200%;
-    overflow: hidden;
-    z-index: -1000;
-    bottom: 0;
-    pointer-events: none;
-    div {
-        height: 100% !important;
-        width: 100%  !important;
-    }
-    canvas { 
-        height: 100% !important;
-        width: 100%  !important;
-    }
-}
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
deleted file mode 100644
index 1fd2fedc3..000000000
--- a/src/client/views/AudioWaveform.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as React from 'react';
-import axios from 'axios';
-import { action, computed, IReactionDisposer, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import Waveform from 'react-audio-waveform';
-import { Doc, NumListCast } from '../../fields/Doc';
-import { List } from '../../fields/List';
-import { listSpec } from '../../fields/Schema';
-import { Cast } from '../../fields/Types';
-import { numberRange } from '../../Utils';
-import './AudioWaveform.scss';
-import { Colors } from './global/globalEnums';
-
-/**
- * AudioWaveform
- *
- * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
- * Uses react-audio-waveform package.
- * Bins the audio data into audioBuckets which are passed to package to render the lines.
- * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
- */
-
-export interface AudioWaveformProps {
-    duration: number; // length of media clip
-    rawDuration: number; // length of underlying media data
-    mediaPath: string;
-    layoutDoc: Doc;
-    clipStart: number;
-    clipEnd: number;
-    zoomFactor: number;
-    PanelHeight: number;
-    PanelWidth: number;
-    fieldKey: string;
-}
-
-@observer
-export class AudioWaveform extends React.Component<AudioWaveformProps> {
-    public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
-
-    _disposer: IReactionDisposer | undefined;
-
-    @computed get waveHeight() {
-        return Math.max(50, this.props.PanelHeight);
-    }
-
-    @computed get clipStart() {
-        return this.props.clipStart;
-    }
-    @computed get clipEnd() {
-        return this.props.clipEnd;
-    }
-    @computed get zoomFactor() {
-        return this.props.zoomFactor;
-    }
-
-    @computed get audioBuckets() {
-        return NumListCast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
-    }
-    audioBucketField = (start: number, end: number, zoomFactor: number) => this.props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
-
-    componentWillUnmount() {
-        this._disposer?.();
-    }
-
-    componentDidMount() {
-        this._disposer = reaction(
-            () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }),
-            ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
-                if (!this.props.layoutDoc[fieldKey]) {
-                    // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
-                    const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec('number'));
-                    this.props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this.props.rawDuration) * waveform.length, (clipEnd / this.props.rawDuration) * waveform.length));
-                    setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
-                }
-            },
-            { fireImmediately: true }
-        );
-    }
-
-    // decodes the audio file into peaks for generating the waveform
-    createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
-        axios({ url: this.props.mediaPath, responseType: 'arraybuffer' }).then(response => {
-            const context = new window.AudioContext();
-            context.decodeAudioData(
-                response.data,
-                action(buffer => {
-                    const rawDecodedAudioData = buffer.getChannelData(0);
-                    const startInd = clipStart / this.props.rawDuration;
-                    const endInd = clipEnd / this.props.rawDuration;
-                    const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
-                    const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
-
-                    const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
-                    const brange = Array.from(Array(bucketDataSize));
-                    const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
-                    this.props.layoutDoc[fieldKey] = new List<number>(bucketList);
-                })
-            );
-        });
-    };
-
-    render() {
-        return (
-            <div className="audioWaveform">
-                <Waveform
-                    color={Colors.MEDIUM_BLUE_ALT}
-                    height={this.waveHeight}
-                    barWidth={200 / this.audioBuckets.length}
-                    pos={this.props.duration}
-                    duration={this.props.duration}
-                    peaks={Array.from(this.audioBuckets)}
-                    progressColor={Colors.MEDIUM_BLUE_ALT}
-                />
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 57bc11385..249e2c3a3 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,40 +1,42 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import './ContextMenu.scss';
-import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
-import { Utils } from '../../Utils';
+import * as React from 'react';
 import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
 import { SettingsManager } from '../util/SettingsManager';
+import './ContextMenu.scss';
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
 
 @observer
 export class ContextMenu extends React.Component {
     static Instance: ContextMenu;
 
-    @observable private _items: Array<ContextMenuProps> = [];
-    @observable private _pageX: number = 0;
-    @observable private _pageY: number = 0;
-    @observable private _display: boolean = false;
-    @observable private _searchString: string = '';
-    @observable private _showSearch: boolean = false;
+    private _ignoreUp = false;
+    private _reactionDisposer?: IReactionDisposer;
+    private _defaultPrefix: string = '';
+    private _defaultItem: ((name: string) => void) | undefined;
+    private _onDisplay?: () => void = undefined;
+
+    @observable _items: ContextMenuProps[] = observable([]);
+    @observable _pageX: number = 0;
+    @observable _pageY: number = 0;
+    @observable _display: boolean = false;
+    @observable _searchString: string = '';
+    @observable _showSearch: boolean = false;
     // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
-    @observable private _yRelativeToTop: boolean = true;
-    @observable selectedIndex = -1;
+    @observable _yRelativeToTop: boolean = true;
+    @observable _selectedIndex = -1;
 
-    @observable private _width: number = 0;
-    @observable private _height: number = 0;
+    @observable _width: number = 0;
+    @observable _height: number = 0;
 
-    @observable private _mouseX: number = -1;
-    @observable private _mouseY: number = -1;
-    @observable private _shouldDisplay: boolean = false;
-    private _ignoreUp = false;
-    private _reactionDisposer?: IReactionDisposer;
+    @observable _mouseX: number = -1;
+    @observable _mouseY: number = -1;
+    @observable _shouldDisplay: boolean = false;
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
-
+        makeObservable(this);
         ContextMenu.Instance = this;
     }
 
@@ -74,36 +76,27 @@ export class ContextMenu extends React.Component {
         this._reactionDisposer?.();
     }
 
-    @action
-    componentDidMount = () => {
+    componentDidMount() {
         document.addEventListener('pointerdown', this.onPointerDown, true);
         document.addEventListener('pointerup', this.onPointerUp);
-    };
+    }
 
     @action
     clearItems() {
-        this._items = [];
+        this._items.length = 0;
         this._defaultPrefix = '';
         this._defaultItem = undefined;
     }
 
-    _defaultPrefix: string = '';
-    _defaultItem: ((name: string) => void) | undefined;
-
-    findByDescription = (target: string, toLowerCase = false) => {
-        return this._items.find(menuItem => {
-            let reference = menuItem.description;
-            toLowerCase && (reference = reference.toLowerCase());
-            return reference === target;
-        });
-    };
+    findByDescription = (target: string, toLowerCase = false) =>
+        this._items.find(menuItem => 
+            (toLowerCase ?  menuItem.description.toLowerCase() : menuItem.description) === target); // prettier-ignore
 
     @action
     addItem(item: ContextMenuProps) {
-        if (this._items.indexOf(item) === -1) {
-            this._items.push(item);
-        }
+        !this._items.includes(item) && this._items.push(item);
     }
+
     @action
     moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
         const curInd = this._items.findIndex(i => i.description === item.description);
@@ -111,6 +104,7 @@ export class ContextMenu extends React.Component {
         const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
         this._items.splice(afterInd, 0, item);
     }
+
     @action
     setDefaultItem(prefix: string, item: (name: string) => void) {
         this._defaultPrefix = prefix;
@@ -126,7 +120,6 @@ export class ContextMenu extends React.Component {
         return this._pageY + this._height > window.innerHeight - ContextMenu.buffer ? window.innerHeight - ContextMenu.buffer - this._height : Math.max(0, this._pageY);
     }
 
-    _onDisplay?: () => void = undefined;
     @action
     displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
         //maxX and maxY will change if the UI/font size changes, but will work for any amount
@@ -201,7 +194,7 @@ export class ContextMenu extends React.Component {
                     <div className="contextMenu-description">{value.join(' -> ')}</div>
                 </div>
             ) : (
-                <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+                <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this._selectedIndex} />
             )
         );
     }
@@ -211,7 +204,8 @@ export class ContextMenu extends React.Component {
     }
 
     render() {
-        return !this._display ? null : (
+        console.log('DISPLAY = ' + this._display);
+        return (
             <div
                 className="contextMenu-cont"
                 ref={action((r: any) => {
@@ -221,6 +215,7 @@ export class ContextMenu extends React.Component {
                     }
                 })}
                 style={{
+                    display: this._display ? '' : 'none',
                     left: this.pageX,
                     ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }),
                     background: SettingsManager.userBackgroundColor,
@@ -242,17 +237,17 @@ export class ContextMenu extends React.Component {
     @action
     onKeyDown = (e: React.KeyboardEvent) => {
         if (e.key === 'ArrowDown') {
-            if (this.selectedIndex < this.flatItems.length - 1) {
-                this.selectedIndex++;
+            if (this._selectedIndex < this.flatItems.length - 1) {
+                this._selectedIndex++;
             }
             e.preventDefault();
         } else if (e.key === 'ArrowUp') {
-            if (this.selectedIndex > 0) {
-                this.selectedIndex--;
+            if (this._selectedIndex > 0) {
+                this._selectedIndex--;
             }
             e.preventDefault();
         } else if (e.key === 'Enter' || e.key === 'Tab') {
-            const item = this.flatItems[this.selectedIndex];
+            const item = this.flatItems[this._selectedIndex];
             if (item) {
                 item.event({ x: this.pageX, y: this.pageY });
             } else if (this._searchString.startsWith(this._defaultPrefix)) {
@@ -268,12 +263,12 @@ export class ContextMenu extends React.Component {
     onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         this._searchString = e.target.value;
         if (!this._searchString) {
-            this.selectedIndex = -1;
+            this._selectedIndex = -1;
         } else {
-            if (this.selectedIndex === -1) {
-                this.selectedIndex = 0;
+            if (this._selectedIndex === -1) {
+                this._selectedIndex = 0;
             } else {
-                this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex);
+                this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
             }
         }
     };
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 6d97d965e..59b223c14 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,12 +1,11 @@
 import * as React from 'react';
-import { observable, action, runInAction } from 'mobx';
+import { observable, action, runInAction, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { UndoManager } from '../util/UndoManager';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
 import { SettingsManager } from '../util/SettingsManager';
+import { copyProps } from '../../Utils';
 
 export interface OriginalMenuProps {
     description: string;
@@ -32,9 +31,20 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
     @observable private _items: Array<ContextMenuProps> = [];
     @observable private overItem = false;
 
-    @action
+    _prevProps: ContextMenuProps & { selected?: boolean };
+    @observable _props: ContextMenuProps & { selected?: boolean };
+    constructor(props: ContextMenuProps & { selected?: boolean }) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentDidMount() {
-        this._items.length = 0;
+        runInAction(() => this._items.length = 0);
         if ((this.props as SubmenuProps)?.subitems) {
             (this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
         }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index d104eb90c..2ce0c085a 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction, untracked } from 'mobx';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
@@ -21,17 +21,26 @@ export interface DocComponentProps {
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends React.Component<React.PropsWithChildren<P>> {
+        @observable _props!: React.PropsWithChildren<P>;
+        constructor(props: React.PropsWithChildren<P>) {
+            super(props);
+            this._props = props;
+            makeObservable(this);
+        }
+        componentDidUpdate() {
+            // untracked(() => (this._props = this.props));
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
-            return this.props.Document;
+            return this._props.Document;
         }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
         @computed get layoutDoc() {
-            return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.());
+            return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.());
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this.props.Document[DocData] as Doc;
+            return this._props.Document[DocData] as Doc;
         }
     }
     return Component;
@@ -49,22 +58,28 @@ interface ViewBoxBaseProps {
 }
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends React.Component<React.PropsWithChildren<P>> {
+        @observable _props: React.PropsWithChildren<P>;
+        constructor(props: React.PropsWithChildren<P>) {
+            super(props);
+            this._props = props;
+            makeObservable(this);
+        }
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         //@computed get Document(): T { return schemaCtor(this.props.Document); }
-        @computed get Document() {
-            return this.props.Document;
+        get Document() {
+            return this._props.Document;
         }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
-        @computed get layoutDoc() {
-            return Doc.Layout(this.props.Document);
+        get layoutDoc() {
+            return Doc.Layout(this.Document);
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
-        @computed get dataDoc() {
-            return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
+        get dataDoc() {
+            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
         }
         // key where data is stored
-        @computed get fieldKey() {
-            return this.props.fieldKey;
+        get fieldKey() {
+            return this._props.fieldKey;
         }
     }
     return Component;
@@ -85,24 +100,30 @@ export interface ViewBoxAnnotatableProps {
 }
 export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
     class Component extends React.Component<React.PropsWithChildren<P>> {
+        @observable _props: React.PropsWithChildren<P>;
+        constructor(props: React.PropsWithChildren<P>) {
+            super(props);
+            this._props = props;
+            makeObservable(this);
+        }
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         @computed get Document() {
-            return this.props.Document;
+            return this._props.Document;
         }
         // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info
         @computed get layoutDoc() {
-            return Doc.Layout(this.props.Document);
+            return Doc.Layout(this.Document);
         }
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() {
-            return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
+            return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
         }
 
         // key where data is stored
         @computed get fieldKey() {
-            return this.props.fieldKey;
+            return this._props.fieldKey;
         }
 
         isAnyChildContentActive = () => this._isAnyChildContentActive;
@@ -136,7 +157,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                 if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
                     (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection();
                 } else {
-                    this.isAnyChildContentActive() && this.props.select(false);
+                    this.isAnyChildContentActive() && this._props.select(false);
                 }
                 return true;
             }
@@ -149,7 +170,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         // moving it into the target.
         @action.bound
         moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => {
-            if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
+            if (Doc.AreProtosEqual(this._props.Document, targetCollection)) {
                 return true;
             }
             const first = doc instanceof Doc ? doc : doc[0];
@@ -161,7 +182,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         @action.bound
         addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => {
             const docs = doc instanceof Doc ? [doc] : doc;
-            if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
+            if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) {
                 return false;
             }
             const targetDataDoc = this.dataDoc;
@@ -190,7 +211,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             return true;
         };
 
-        whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+        whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
     }
     return Component;
 }
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a64722a0b..47ec0f1b4 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
@@ -60,8 +60,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     public static hasPushedHack = false;
     public static hasPulledHack = false;
 
+    @observable _props: { views: () => (DocumentView | undefined)[] };
     constructor(props: { views: () => (DocumentView | undefined)[] }) {
         super(props);
+        this._props = props;
+        makeObservable(this);
         runInAction(() => (DocumentButtonBar.Instance = this));
     }
 
@@ -111,12 +114,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     });
 
     get view0() {
-        return this.props.views()?.[0];
+        return this._props.views()?.[0];
     }
 
     @computed
     get considerGoogleDocsPush() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
         const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
         return !targetDoc ? null : (
@@ -143,7 +146,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     @computed
     get considerGoogleDocsPull() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         const dataDoc = targetDoc && Doc.GetProto(targetDoc);
         const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
 
@@ -214,7 +217,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @observable subFollow = '';
     @computed
     get followLinkButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => {
             const tooltip = `Follow ${this.subPin}documents`;
             return !tooltip ? null : (
@@ -229,7 +232,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             onPointerEnter={action(e => (this.subPin = allDocs ? 'All ' : ''))}
                             onPointerLeave={action(e => (this.subPin = ''))}
                             onClick={e => {
-                                this.props.views().forEach(dv => click(dv!.Document));
+                                this._props.views().forEach(dv => click(dv!.Document));
                                 e.stopPropagation();
                             }}
                         />
@@ -243,7 +246,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 <div
                     className="documentButtonBar-icon documentButtonBar-follow"
                     style={{ backgroundColor: followLink ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: followLink ? Colors.BLACK : Colors.WHITE }}
-                    onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
+                    onClick={undoBatch(e => this._props.views().map(view => view?.docView?.toggleFollowLink(undefined, false)))}>
                     <div className="documentButtonBar-followTypes">
                         {followBtn(
                             true,
@@ -260,7 +263,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     @observable subLink = '';
     @computed get linkButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         return !targetDoc || !this.view0 ? null : (
             <div className="documentButtonBar-icon documentButtonBar-link">
                 <div className="documentButtonBar-linkTypes">
@@ -304,7 +307,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             onPointerLeave={action(e => (this.subEndLink = ''))}
                             onClick={e => {
                                 this.view0 &&
-                                    DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, {
+                                    DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.Document, true, this.view0, {
                                         pinDocLayout: pinLayout,
                                         pinData: !pinContent ? {} : { poslayoutview: true, dataannos: true, dataview: pinContent },
                                     } as PinProps);
@@ -331,7 +334,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @observable subPin = '';
     @computed
     get pinButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         const pinBtn = (pinLayoutView: boolean, pinContentView: boolean, icon: IconProp) => {
             const tooltip = `Pin Document and Save ${this.subPin} to trail`;
             return !tooltip ? null : (
@@ -353,7 +356,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                             )}
                             onPointerLeave={action(e => (this.subPin = ''))}
                             onClick={e => {
-                                const docs = this.props
+                                const docs = this._props
                                     .views()
                                     .filter(v => v)
                                     .map(dv => dv!.Document);
@@ -376,7 +379,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 <div
                     className="documentButtonBar-icon documentButtonBar-pin"
                     onClick={e => {
-                        const docs = this.props
+                        const docs = this._props
                             .views()
                             .filter(v => v)
                             .map(dv => dv!.Document);
@@ -396,7 +399,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     @computed
     get shareButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">{'Open Sharing Manager'}</div>}>
                 <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
@@ -408,10 +411,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     @computed
     get menuButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">{`Open Context Menu`}</div>}>
-                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onClick={this.openContextMenu}>
+                <div className="documentButtonBar-icon" style={{ color: 'white', cursor: 'pointer' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => this.openContextMenu(e))}>
                     <FontAwesomeIcon className="documentdecorations-icon" icon="bars" />
                 </div>
             </Tooltip>
@@ -427,10 +430,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         anchorPoint={anchorPoints.LEFT_TOP}
                         content={
                             <MetadataEntryMenu
-                                docs={this.props
+                                docs={this._props
                                     .views()
                                     .filter(dv => dv)
-                                    .map(dv => dv!.props.Document)}
+                                    .map(dv => dv!.Document)}
                                 suggestWithFunction
                             /> // tfs: @bcz This might need to be the data document?
                         }>
@@ -447,7 +450,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     _stopFunc: () => void = emptyFunction;
     @computed
     get recordButton() {
-        const targetDoc = this.view0?.props.Document;
+        const targetDoc = this.view0?.Document;
         return !targetDoc ? null : (
             <Tooltip title={<div className="dash-tooltip">Press to record audio annotation</div>}>
                 <div
@@ -455,7 +458,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }}
                     onPointerDown={action((e: React.PointerEvent) => {
                         this._isRecording = true;
-                        this.props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
+                        this._props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction));
                         const b = UndoManager.StartBatch('Recording');
                         setupMoveUpEvents(
                             this,
@@ -482,8 +485,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     onEmbedButtonMoved = () => {
         if (this._dragRef.current) {
             const dragDocView = this.view0!;
-            const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
-            const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
+            const [left, top] = dragDocView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
             dragData.defaultDropAction = 'embed';
             dragData.canEmbed = true;
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
@@ -497,7 +500,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
     @computed
     get templateButton() {
         const view0 = this.view0;
-        const views = this.props.views();
+        const views = this._props.views();
         return !view0 ? null : (
             <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
                 <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
@@ -522,7 +525,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         );
     }
 
-    openContextMenu = (e: React.MouseEvent) => {
+    openContextMenu = (e: PointerEvent) => {
         let child = SelectionManager.Views()[0].ContentDiv!.children[0];
         while (child.children.length) {
             const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
@@ -559,7 +562,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
 
     @action
     toggleTrail = (e: React.PointerEvent) => {
-        const rootView = this.props.views()[0];
+        const rootView = this._props.views()[0];
         const doc = rootView?.Document;
         if (doc) {
             const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc;
@@ -569,12 +572,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 anchor.presentationTrail = trail;
             }
             Doc.ActivePresentation = trail;
-            this.props.views().lastElement()?.props.addDocTab(trail, OpenWhere.replaceRight);
+            this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight);
         }
         e.stopPropagation();
     };
     render() {
-        const doc = this.view0?.props.Document;
+        const doc = this.view0?.Document;
         if (!doc || !this.view0) return null;
 
         const isText = () => doc[this.view0!.LayoutFieldKey] instanceof RichTextField;
@@ -589,9 +592,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                     <div style={{ position: 'absolute', zIndex: 1000 }}>
                         <LinkPopup
                             key="popup"
-                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this.props.views().lastElement()?.Document.onClick))}
-                            linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
-                            linkFrom={() => this.props.views().lastElement()?.Document}
+                            linkCreated={link => (link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick))}
+                            linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)}
+                            linkFrom={() => this._props.views().lastElement()?.Document}
                         />
                     </div>
                 ) : (
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index b4c19df2d..6ef3fcc66 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { IconButton } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { FaUndo } from 'react-icons/fa';
 import { DateField } from '../../fields/DateField';
@@ -34,10 +34,15 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import * as React from 'react';
-import * as _ from 'lodash';
 
+interface DocumentDecorationsProps {
+    PanelWidth: number;
+    PanelHeight: number;
+    boundsLeft: number;
+    boundsTop: number;
+}
 @observer
-export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
+export class DocumentDecorations extends React.Component<DocumentDecorationsProps> {
     static Instance: DocumentDecorations;
     private _resizeHdlId = '';
     private _keyinput = React.createRef<HTMLInputElement>();
@@ -61,8 +66,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @observable private _showRotCenter = false; // whether to show a draggable green dot that represents the center of rotation
     @observable private _rotCenter = [0, 0]; // the center of rotation in object coordinates (0,0) = object center (not top left!)
 
-    constructor(props: any) {
+    @observable _props: React.PropsWithChildren<DocumentDecorationsProps>;
+    constructor(props: React.PropsWithChildren<DocumentDecorationsProps>) {
         super(props);
+        this._props = props;
+        makeObservable(this);
         DocumentDecorations.Instance = this;
         document.addEventListener('pointermove', // show decorations whenever pointer moves outside of selection bounds.
             action(e => {
@@ -78,8 +86,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @computed get ClippedBounds() {
         const bounds = this.Bounds;
-        const leftBounds = this.props.boundsLeft;
-        const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop;
+        const leftBounds = this._props.boundsLeft;
+        const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop;
         bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
         bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
         const borderRadiusDraggerWidth = 15;
@@ -92,7 +100,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         return (LinkFollower.IsFollowing || DocumentView.ExploreMode) ? 
             { x: 0, y: 0, r: 0, b: 0 }
             : SelectionManager.Views()
-                .filter(dv => dv.props.renderDepth > 0)
+                .filter(dv => dv._props.renderDepth > 0)
                 .map(dv => dv.getBounds())
                 .reduce((bounds, rect) => !rect ? bounds
                             : { x: Math.min(rect.left, bounds.x),
@@ -198,12 +206,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (containers.size > 1) return false;
         const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
         const dragData = new DragManager.DocumentDragData(
-            SelectionManager.Views().map(dv => dv.props.Document),
-            dragDocView.props.dropAction
+            SelectionManager.Views().map(dv => dv.Document),
+            dragDocView._props.dropAction
         );
-        dragData.offset = dragDocView.props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
-        dragData.moveDocument = dragDocView.props.moveDocument;
-        dragData.removeDocument = dragDocView.props.removeDocument;
+        dragData.offset = dragDocView._props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+        dragData.moveDocument = dragDocView._props.moveDocument;
+        dragData.removeDocument = dragDocView._props.removeDocument;
         dragData.isDocDecorationMove = true;
         dragData.canEmbed = dragTitle;
         this._hidden = true;
@@ -223,7 +231,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     _deleteAfterIconify = false;
     _iconifyBatch: UndoManager.Batch | undefined;
     onCloseClick = (forceDeleteOrIconify: boolean | undefined) => {
-        const views = SelectionManager.Views().filter(v => v && v.props.renderDepth > 0);
+        const views = SelectionManager.Views().filter(v => v && v._props.renderDepth > 0);
         if (forceDeleteOrIconify === false && this._iconifyBatch) return;
         this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false;
         var iconifyingCount = views.length;
@@ -231,11 +239,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
                 if (this._deleteAfterIconify) {
                     views.forEach(iconView => {
-                        Doc.setNativeView(iconView.props.Document);
-                        if (iconView.props.Document.activeFrame) {
-                            iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
+                        Doc.setNativeView(iconView.Document);
+                        if (iconView.Document.activeFrame) {
+                            iconView.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation.
                         } else {
-                            iconView.props.removeDocument?.(iconView.props.Document);
+                            iconView._props.removeDocument?.(iconView.Document);
                         }
                     });
                     views.forEach(SelectionManager.DeselectView);
@@ -296,7 +304,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         SelectionManager.DeselectAll();
     };
 
-    onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?.props.docViewPath?.().lastElement()?.select(false);
+    onSelectContainerDocClick = () => SelectionManager.Views()?.[0]?._props.docViewPath?.().lastElement()?.select(false);
     /**
      * sets up events when user clicks on the border radius editor
      */
@@ -344,7 +352,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
-        const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+        const newloccentern = seldocview._props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
         seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -376,7 +384,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const seldocview = SelectionManager.Views()[0];
         SelectionManager.Views().forEach(dv => {
             const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
-            const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+            const localRotCtr = dv._props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
             const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
@@ -454,7 +462,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
             return [a[0] + atob[0] * t, a[1] + atob[1] * t];
         };
-        const tl = docView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const tl = docView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -475,7 +483,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
             this._interactionLock = true;
             this._snapPt = thisPt;
-            e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.props.Document) && docView.toggleNativeDimensions()));
+            e.ctrlKey && (SelectionManager.Views().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()));
             const fixedAspect = SelectionManager.Docs().some(this.hasFixedAspect);
             const scaleAspect = {x:scale.x === 1 && fixedAspect ? scale.y : scale.x, y: scale.x !== 1 && fixedAspect ? scale.x : scale.y};
             SelectionManager.Views().forEach(docView => 
@@ -522,7 +530,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            const refCent = docView.props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const refCent = docView._props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
@@ -615,7 +623,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
             }
             if (this._titleControlString.startsWith('#')) {
-                return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
+                return Field.toString(selected.Document[this._titleControlString.substring(1)] as Field) || '-unset-';
             }
             return this._accumulatedTitle;
         }
@@ -625,7 +633,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     @computed get rotCenter() {
         const lastView = SelectionManager.Views().lastElement();
         if (lastView) {
-            const invXf = lastView.props.ScreenToLocalTransform().inverse();
+            const invXf = lastView._props.ScreenToLocalTransform().inverse();
             const seldoc = lastView.layoutDoc;
             const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
@@ -648,16 +656,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         var shareSymbolIcon = ReverseHierarchyMap.get(shareMode)?.image;
 
         // hide the decorations if the parent chooses to hide it or if the document itself hides it
-        const hideDecorations = SnappingManager.GetIsResizing() || seldocview.props.hideDecorations || seldocview.Document.layout_hideDecorations;
+        const hideDecorations = SnappingManager.GetIsResizing() || seldocview._props.hideDecorations || seldocview.Document.layout_hideDecorations;
         const hideResizers =
-            ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview.props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
-        const hideTitle = this._showNothing || hideDecorations || seldocview.props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
-        const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
+            ![AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(seldocview.Document)) || hideDecorations || seldocview._props.hideResizeHandles || seldocview.Document.layout_hideResizeHandles || this._isRounding || this._isRotating;
+        const hideTitle = this._showNothing || hideDecorations || seldocview._props.hideDecorationTitle || seldocview.Document.layout_hideDecorationTitle || this._isRounding || this._isRotating;
+        const hideDocumentButtonBar = hideDecorations || seldocview._props.hideDocumentButtonBar || seldocview.Document.layout_hideDocumentButtonBar || this._isRounding || this._isRotating;
         // if multiple documents have been opened at the same time, then don't show open button
         const hideOpenButton =
             this._showNothing ||
             hideDecorations ||
-            seldocview.props.hideOpenButton ||
+            seldocview._props.hideOpenButton ||
             seldocview.Document.layout_hideOpenButton ||
             SelectionManager.Views().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) ||
             this._isRounding ||
@@ -667,10 +675,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             hideDecorations ||
             this._isRounding ||
             this._isRotating ||
-            seldocview.props.hideDeleteButton ||
+            seldocview._props.hideDeleteButton ||
             seldocview.Document.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().dataDoc) : AclEdit;
+                const collectionAcl = docView._props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView._props.docViewPath().lastElement().dataDoc) : AclEdit;
                 return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin;
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -798,7 +806,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     <div key="br" className="documentDecorations-bottomRightResizer" onPointerDown={this.onPointerDown} />
                                 </>
                             )}
-                            {seldocview.props.renderDepth <= 1 || !seldocview.props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
+                            {seldocview._props.renderDepth <= 1 || !seldocview._props.docViewPath().lastElement() ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectContainerDocClick, 'tap to select containing document')}
                             {useRounding && (
                                 <div
                                     className="documentDecorations-borderRadius"
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 6bc0e5424..3db77be71 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,11 +1,13 @@
 import * as React from 'react';
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Autosuggest from 'react-autosuggest';
 import { ObjectField } from '../../fields/ObjectField';
 import './EditableView.scss';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
 import { OverlayView } from './OverlayView';
+import { copyProps } from '../../Utils';
+import { FieldView, FieldViewProps } from './nodes/FieldView';
 
 export interface EditableProps {
     /**
@@ -26,6 +28,7 @@ export interface EditableProps {
      * The contents to render when not editing
      */
     contents: any;
+    fieldContents?: FieldViewProps;
     fontStyle?: string;
     fontSize?: number;
     height?: number | 'auto';
@@ -64,9 +67,13 @@ export class EditableView extends React.Component<EditableProps> {
     _editingDisposer?: IReactionDisposer;
     @observable _editing: boolean = false;
 
+    _prevProps: EditableProps;
+    @observable _props: EditableProps;
     constructor(props: EditableProps) {
         super(props);
-        this._editing = this.props.editing ? true : false;
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._editing = this._props.editing ? true : false;
     }
 
     componentDidMount(): void {
@@ -89,12 +96,12 @@ export class EditableView extends React.Component<EditableProps> {
         );
     }
 
-    @action
     componentDidUpdate() {
-        if (this._editing && this.props.editing === false) {
+        copyProps(this);
+        if (this._editing && this._props.editing === false) {
             this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
-        } else if (this.props.editing !== undefined) {
-            this._editing = this.props.editing;
+        } else if (this._props.editing !== undefined) {
+            this._editing = this._props.editing;
         }
     }
 
@@ -120,28 +127,28 @@ export class EditableView extends React.Component<EditableProps> {
             case 'Tab':
                 e.stopPropagation();
                 this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
-                this.props.OnTab?.(e.shiftKey);
+                this._props.OnTab?.(e.shiftKey);
                 break;
             case 'Backspace':
                 e.stopPropagation();
-                if (!e.currentTarget.value) this.props.OnEmpty?.();
+                if (!e.currentTarget.value) this._props.OnEmpty?.();
                 break;
             case 'Enter':
-                if (this.props.allowCRs !== true) {
+                if (this._props.allowCRs !== true) {
                     e.stopPropagation();
                     if (!e.ctrlKey) {
                         this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
-                    } else if (this.props.OnFillDown) {
-                        this.props.OnFillDown(e.currentTarget.value);
+                    } else if (this._props.OnFillDown) {
+                        this._props.OnFillDown(e.currentTarget.value);
                         this._editing = false;
-                        this.props.isEditingCallback?.(false);
+                        this._props.isEditingCallback?.(false);
                     }
                 }
                 break;
             case 'Escape':
                 e.stopPropagation();
                 this._editing = false;
-                this.props.isEditingCallback?.(false);
+                this._props.isEditingCallback?.(false);
                 break;
             case 'ArrowUp':
             case 'ArrowDown':
@@ -155,30 +162,30 @@ export class EditableView extends React.Component<EditableProps> {
             case 'Control':
                 break;
             case ':':
-                if (this.props.menuCallback) {
+                if (this._props.menuCallback) {
                     e.stopPropagation();
-                    this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
+                    this._props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y);
                     break;
                 }
 
             default:
-                if (this.props.textCallback?.(e.key)) {
+                if (this._props.textCallback?.(e.key)) {
                     e.stopPropagation();
                     this._editing = false;
-                    this.props.isEditingCallback?.(false);
+                    this._props.isEditingCallback?.(false);
                 }
         }
     };
 
     @action
     onClick = (e: React.MouseEvent) => {
-        if (this.props.editing !== false) {
+        if (this._props.editing !== false) {
             e.nativeEvent.stopPropagation();
-            if (this._ref.current && this.props.showMenuOnLoad) {
-                this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
+            if (this._ref.current && this._props.showMenuOnLoad) {
+                this._props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y);
             } else {
                 this._editing = true;
-                this.props.isEditingCallback?.(true);
+                this._props.isEditingCallback?.(true);
             }
             // e.stopPropagation();
         }
@@ -186,17 +193,17 @@ export class EditableView extends React.Component<EditableProps> {
 
     @action
     finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
-        if (this.props.SetValue(value, shiftDown, enterKey)) {
+        if (this._props.SetValue(value, shiftDown, enterKey)) {
             this._editing = false;
-            this.props.isEditingCallback?.(false);
+            this._props.isEditingCallback?.(false);
         } else {
             this._editing = false;
-            this.props.isEditingCallback?.(false);
+            this._props.isEditingCallback?.(false);
             !lostFocus &&
                 setTimeout(
                     action(() => {
                         this._editing = true;
-                        this.props.isEditingCallback?.(true);
+                        this._props.isEditingCallback?.(true);
                     }),
                     0
                 );
@@ -215,9 +222,9 @@ export class EditableView extends React.Component<EditableProps> {
     };
 
     renderEditor() {
-        return this.props.autosuggestProps ? (
+        return this._props.autosuggestProps ? (
             <Autosuggest
-                {...this.props.autosuggestProps.autosuggestProps}
+                {...this._props.autosuggestProps.autosuggestProps}
                 inputProps={{
                     className: 'editableView-input',
                     onKeyDown: this.onKeyDown,
@@ -228,19 +235,19 @@ export class EditableView extends React.Component<EditableProps> {
                     onClick: this.stopPropagation,
                     onPointerUp: this.stopPropagation,
                     onKeyPress: this.stopPropagation,
-                    value: this.props.autosuggestProps.value,
+                    value: this._props.autosuggestProps.value,
                     // @ts-ignore
-                    onChange: this.props.autosuggestProps.onChange,
+                    onChange: this._props.autosuggestProps.onChange,
                 }}
             />
-        ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? (
+        ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? (
             <input
                 className="editableView-input"
                 ref={r => (this._inputref = r)}
-                style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }}
-                placeholder={this.props.placeholder}
+                style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }}
+                placeholder={this._props.placeholder}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
-                defaultValue={this.props.GetValue()}
+                defaultValue={this._props.GetValue()}
                 autoFocus={true}
                 onChange={this.onChange}
                 onKeyDown={this.onKeyDown}
@@ -253,10 +260,10 @@ export class EditableView extends React.Component<EditableProps> {
             <textarea
                 className="editableView-input"
                 ref={r => (this._inputref = r)}
-                style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minHeight: `min(100%, ${(this.props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this.props.background }}
-                placeholder={this.props.placeholder}
+                style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this._props.background }}
+                placeholder={this._props.placeholder}
                 onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)}
-                defaultValue={this.props.GetValue()}
+                defaultValue={this._props.GetValue()}
                 autoFocus={true}
                 onChange={this.onChange}
                 onKeyDown={this.onKeyDown}
@@ -269,9 +276,9 @@ export class EditableView extends React.Component<EditableProps> {
     }
 
     render() {
-        const gval = this.props.GetValue()?.replace(/\n/g, '\\r\\n');
+        const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
         if (this._editing && gval !== undefined) {
-            return this.props.sizeToContent ? (
+            return this._props.sizeToContent ? (
                 <div style={{ display: 'grid', minWidth: 100 }}>
                     <div style={{ display: 'inline-block', position: 'relative', height: 0, width: '100%', overflow: 'hidden' }}>{gval}</div>
                     {this.renderEditor()}
@@ -280,30 +287,30 @@ export class EditableView extends React.Component<EditableProps> {
                 this.renderEditor()
             );
         }
-        setTimeout(() => this.props.autosuggestProps?.resetValue());
-        return this.props.contents instanceof ObjectField ? null : (
+        setTimeout(() => this._props.autosuggestProps?.resetValue());
+        return this._props.contents instanceof ObjectField ? null : (
             <div
-                className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
+                className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}
                 ref={this._ref}
                 style={{
-                    display: this.props.display, //
-                    textOverflow: this.props.overflow,
+                    display: this._props.display, //
+                    textOverflow: this._props.overflow,
                     minHeight: '10px',
-                    whiteSpace: this.props.oneLine ? 'nowrap' : 'pre-line',
-                    height: this.props.height,
-                    maxHeight: this.props.maxHeight,
-                    fontStyle: this.props.fontStyle,
-                    fontSize: this.props.fontSize,
+                    whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line',
+                    height: this._props.height,
+                    maxHeight: this._props.maxHeight,
+                    fontStyle: this._props.fontStyle,
+                    fontSize: this._props.fontSize,
                 }}
                 //onPointerDown={this.stopPropagation}
                 onClick={this.onClick}
-                placeholder={this.props.placeholder}>
+                placeholder={this._props.placeholder}>
                 <span
                     style={{
-                        fontStyle: this.props.fontStyle,
-                        fontSize: this.props.fontSize,
+                        fontStyle: this._props.fontStyle,
+                        fontSize: this._props.fontSize,
                     }}>
-                    {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}
+                    {this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : this._props.placeholder?.valueOf()}
                 </span>
             </div>
         );
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 9efa7baac..884c74f9b 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -12,7 +12,7 @@ import { DocumentManager } from '../util/DocumentManager';
 import { undoBatch } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
 import { DocumentView } from './nodes/DocumentView';
-import _ = require('lodash');
+import * as _ from 'lodash';
 
 export class InkStrokeProperties {
     static _Instance: InkStrokeProperties | undefined;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 41a2507f9..d34df8742 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -141,7 +141,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     public static toggleMask = action((inkDoc: Doc) => {
         inkDoc.stroke_isInkMask = !inkDoc.stroke_isInkMask;
     });
-    @observable controlUndo: UndoManager.Batch | undefined;
+    @observable controlUndo: UndoManager.Batch | undefined = undefined;
     /**
      * Drags the a simple bezier segment of the stroke.
      * Also adds a control point when double clicking on the stroke.
@@ -466,7 +466,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 <svg
                     className="inkStroke"
                     style={{
-                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.().props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
                         cursor: this.props.isSelected() ? 'default' : undefined,
                     }}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 50feccce2..dae2490c9 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,11 +3,11 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons';
 import * as far from '@fortawesome/free-regular-svg-icons';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import '../../../node_modules/browndash-components/dist/styles/global.min.css';
-import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
+import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import 'normalize.css';
 import * as React from 'react';
+import '../../../node_modules/browndash-components/dist/styles/global.min.css';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { DocCast, StrCast } from '../../fields/Types';
 import { emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
@@ -48,7 +48,7 @@ import { LightboxView } from './LightboxView';
 import { LinkMenu } from './linking/LinkMenu';
 import './MainView.scss';
 import { AudioBox } from './nodes/AudioBox';
-import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
@@ -131,14 +131,14 @@ export class MainView extends React.Component {
     }
 
     headerBarDocWidth = () => this.mainDocViewWidth();
-    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0);
+    headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.Instance?.headerBarHeight ?? 0);
     topMenuHeight = () => (this._hideUI ? 0 : 35);
     topMenuWidth = returnZero; // value is ignored ...
     leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', '')));
     leftMenuHeight = () => this._dashUIHeight;
     leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth;
     leftMenuFlyoutHeight = () => this._dashUIHeight;
-    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0));
+    propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0));
     propertiesHeight = () => this._dashUIHeight;
     mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth();
     mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
@@ -232,6 +232,7 @@ export class MainView extends React.Component {
 
     constructor(props: Readonly<{}>) {
         super(props);
+        makeObservable(this);
         DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
         MainView.Instance = this;
         DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -683,9 +684,9 @@ export class MainView extends React.Component {
         setupMoveUpEvents(
             this,
             e,
-            action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
-            action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)),
-            action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
+            action(e => ((SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)),
+            action(() => SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0)),
+            action(() => (SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)),
             false
         );
     };
@@ -1037,7 +1038,7 @@ export class MainView extends React.Component {
                 <ComponentDecorations boundsLeft={this.leftScreenOffsetOfMainDocView} boundsTop={this.topOfMainDocContent} />
                 {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
-                {DocumentLinksButton.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null}
+                {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
                 {this.linkDocPreview}
 
                 {((page: string) => {
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 67b722e7a..af7f38937 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,8 +1,6 @@
 import { isDark } from 'browndash-components';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
 import { SettingsManager } from '../util/SettingsManager';
 import './MainViewModal.scss';
 
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index a241e3a2b..3f0db4258 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,12 +1,12 @@
-import { action, computed, observable, ObservableMap } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
-import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { NumCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { unimplementedFunction, Utils } from '../../Utils';
+import { copyProps, unimplementedFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { DragManager } from '../util/DragManager';
 import { FollowLinkScript } from '../util/LinkFollower';
@@ -15,7 +15,6 @@ import './MarqueeAnnotator.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
-import * as React from 'react';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
@@ -46,6 +45,17 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     @computed get top() { return Math.min(this._start.y, this._start.y + this._height); } // prettier-ignore
     @computed get left() { return Math.min(this._start.x, this._start.x + this._width);} // prettier-ignore
 
+    _prevProps: React.PropsWithChildren<MarqueeAnnotatorProps>;
+    @observable _props: React.PropsWithChildren<MarqueeAnnotatorProps>;
+    constructor(props: React.PropsWithChildren<MarqueeAnnotatorProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidMount() {
+        copyProps(this);
+    }
+
     @action
     static clearAnnotations(savedAnnotations: ObservableMap<number, HTMLDivElement[]>) {
         AnchorMenu.Instance.Status = 'marquee';
@@ -56,7 +66,6 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     }
 
     @undoBatch
-    @action
     makeAnnotationDocument = (color: string, isLinkButton?: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
         const savedAnnoMap = savedAnnotations?.values() && Array.from(savedAnnotations?.values()).length ? savedAnnotations : this.props.savedAnnotations();
         if (savedAnnoMap.size === 0) return undefined;
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index d1bd0500b..6e1a2cfb1 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../fields/Doc';
@@ -12,30 +12,38 @@ import './PreviewCursor.scss';
 
 @observer
 export class PreviewCursor extends React.Component<{}> {
-    static _onKeyPress?: (e: KeyboardEvent) => void;
-    static _getTransform: () => Transform;
-    static _addDocument: (doc: Doc | Doc[]) => boolean;
-    static _addLiveTextDoc: (doc: Doc) => void;
-    static _nudge?: undefined | ((x: number, y: number) => boolean);
-    static _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
-    @observable static _clickPoint = [0, 0];
-    @observable public static Visible = false;
-    public static Doc: Opt<Doc>;
+    static _instance: PreviewCursor;
+    public static get Instance() {
+        return PreviewCursor._instance;
+    }
+
+    _onKeyPress?: (e: KeyboardEvent) => void;
+    _getTransform?: () => Transform;
+    _addDocument?: (doc: Doc | Doc[]) => boolean;
+    _addLiveTextDoc?: (doc: Doc) => void;
+    _nudge?: undefined | ((x: number, y: number) => boolean);
+    _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
+    @observable _clickPoint: number[];
+    @observable public Visible = false;
+    public Doc: Opt<Doc>;
     constructor(props: any) {
         super(props);
+        PreviewCursor._instance = this;
+        makeObservable(this);
+        this._clickPoint = observable([0, 0]);
         document.addEventListener('keydown', this.onKeyPress);
         document.addEventListener('paste', this.paste, true);
     }
 
     paste = async (e: ClipboardEvent) => {
-        if (PreviewCursor.Visible && e.clipboardData) {
-            const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
-            runInAction(() => (PreviewCursor.Visible = false));
+        if (this.Visible && e.clipboardData) {
+            const newPoint = this._getTransform?.().transformPoint(this._clickPoint[0], this._clickPoint[1]);
+            runInAction(() => (this.Visible = false));
 
             // tests for URL and makes web document
             const re: any = /^https?:\/\//g;
             const plain = e.clipboardData.getData('text/plain');
-            if (plain) {
+            if (plain && newPoint) {
                 // tests for youtube and makes video document
                 if (plain.indexOf('www.youtube.com/watch') !== -1) {
                     const batch = UndoManager.StartBatch('youtube upload');
@@ -47,21 +55,22 @@ export class PreviewCursor extends React.Component<{}> {
                         x: newPoint[0],
                         y: newPoint[1],
                     };
-                    PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end);
+                    this._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, this._addDocument ?? returnFalse).then(batch.end);
                 } else if (re.test(plain)) {
                     const url = plain;
                     if (!url.startsWith(window.location.href)) {
-                        undoBatch(() =>
-                            PreviewCursor._addDocument(
-                                Docs.Create.WebDocument(url, {
-                                    title: url,
-                                    _width: 500,
-                                    _height: 300,
-                                    data_useCors: true,
-                                    x: newPoint[0],
-                                    y: newPoint[1],
-                                })
-                            )
+                        undoBatch(
+                            () =>
+                                this._addDocument?.(
+                                    Docs.Create.WebDocument(url, {
+                                        title: url,
+                                        _width: 500,
+                                        _height: 300,
+                                        data_useCors: true,
+                                        x: newPoint[0],
+                                        y: newPoint[1],
+                                    })
+                                )
                         )();
                     } else alert('cannot paste dash into itself');
                 } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
@@ -70,13 +79,13 @@ export class PreviewCursor extends React.Component<{}> {
                     const strs = docids[0].split(','); // hack!  docids[0] is the top left of the selection rectangle
                     const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length));
                     const pty = Number(strs[1].substring(0, strs[1].length - 1));
-                    Doc.Paste(docids.slice(1), clone, PreviewCursor._addDocument, ptx, pty, newPoint);
+                    this._addDocument && Doc.Paste(docids.slice(1), clone, this._addDocument, ptx, pty, newPoint);
 
                     e.stopPropagation();
                 } else {
                     FormattedTextBox.PasteOnLoad = e;
                     if (e.clipboardData.getData('dash/pdfAnchor')) e.preventDefault();
-                    UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
+                    UndoManager.RunInBatch(() => this._addLiveTextDoc?.(DocUtils.GetNewTextDoc('', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste');
                 }
             }
             //pasting in images
@@ -84,17 +93,19 @@ export class PreviewCursor extends React.Component<{}> {
                 const re: any = /<img src="(.*?)"/g;
                 const arr: any[] = re.exec(e.clipboardData.getData('text/html'));
 
-                undoBatch(() => {
-                    const doc = Docs.Create.ImageDocument(arr[1], {
-                        _width: 300,
-                        title: arr[1],
-                        x: newPoint[0],
-                        y: newPoint[1],
-                    });
-                    ImageUtils.ExtractExif(doc);
-                    PreviewCursor._addDocument(doc);
-                })();
-            } else if (e.clipboardData.items.length) {
+                if (newPoint) {
+                    undoBatch(() => {
+                        const doc = Docs.Create.ImageDocument(arr[1], {
+                            _width: 300,
+                            title: arr[1],
+                            x: newPoint[0],
+                            y: newPoint[1],
+                        });
+                        ImageUtils.ExtractExif(doc);
+                        this._addDocument?.(doc);
+                    })();
+                }
+            } else if (e.clipboardData.items.length && newPoint) {
                 const batch = UndoManager.StartBatch('collection view drop');
                 const files: File[] = [];
                 Array.from(e.clipboardData.items).forEach(item => {
@@ -102,7 +113,7 @@ export class PreviewCursor extends React.Component<{}> {
                     file && files.push(file);
                 });
                 const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] });
-                generatedDocuments.forEach(PreviewCursor._addDocument);
+                this._addDocument && generatedDocuments.forEach(this._addDocument);
                 batch.end();
             }
         }
@@ -135,25 +146,25 @@ export class PreviewCursor extends React.Component<{}> {
         ) {
             if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {
                 //  /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
-                PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e);
-                ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (PreviewCursor.Visible = false);
+                this.Visible && this._onKeyPress?.(e);
+                ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (this.Visible = false);
             }
-        } else if (PreviewCursor.Visible) {
+        } else if (this.Visible) {
             if (e.key === 'ArrowRight') {
-                PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+                this._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === 'ArrowLeft') {
-                PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
+                this._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation();
             } else if (e.key === 'ArrowUp') {
-                PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+                this._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             } else if (e.key === 'ArrowDown') {
-                PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
+                this._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation();
             }
         }
     };
 
     //when focus is lost, this will remove the preview cursor
     @action onBlur = (): void => {
-        PreviewCursor.Visible = false;
+        this.Visible = false;
     };
 
     @action
@@ -167,23 +178,21 @@ export class PreviewCursor extends React.Component<{}> {
         nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean),
         slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>
     ) {
-        this._clickPoint = [x, y];
-        this._onKeyPress = onKeyPress;
-        this._addLiveTextDoc = addLiveText;
-        this._getTransform = getTransform;
-        this._addDocument = addDocument || returnFalse;
-        this._nudge = nudge;
-        this._slowLoadDocuments = slowLoadDocuments;
-        this.Visible = true;
+        const self = PreviewCursor.Instance;
+        if (self) {
+            self._clickPoint = [x, y];
+            self._onKeyPress = onKeyPress;
+            self._addLiveTextDoc = addLiveText;
+            self._getTransform = getTransform;
+            self._addDocument = addDocument || returnFalse;
+            self._nudge = nudge;
+            self._slowLoadDocuments = slowLoadDocuments;
+            self.Visible = true;
+        }
     }
     render() {
-        return !PreviewCursor._clickPoint || !PreviewCursor.Visible ? null : (
-            <div
-                className="previewCursor"
-                onBlur={this.onBlur}
-                tabIndex={0}
-                ref={e => e?.focus()}
-                style={{ color: lightOrDark(PreviewCursor.Doc?.backgroundColor ?? 'white'), transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
+        return !this._clickPoint || !this.Visible ? null : (
+            <div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e?.focus()} style={{ color: lightOrDark(this.Doc?.backgroundColor ?? 'white'), transform: `translate(${this._clickPoint[0]}px, ${this._clickPoint[1]}px)` }}>
                 I
             </div>
         );
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 2c3cd8eac..b4c591b89 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@mui/material';
 import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components';
 import { concat } from 'lodash';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorResult, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
@@ -17,7 +17,7 @@ import { List } from '../../fields/List';
 import { ComputedField } from '../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
 import { GroupManager } from '../util/GroupManager';
@@ -53,8 +53,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     private _widthUndo?: UndoManager.Batch;
 
     public static Instance: PropertiesView | undefined;
-    constructor(props: any) {
+    _prevProps: React.PropsWithChildren<PropertiesViewProps>;
+    @observable _props: React.PropsWithChildren<PropertiesViewProps>;
+    constructor(props: React.PropsWithChildren<PropertiesViewProps>) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         PropertiesView.Instance = this;
     }
 
@@ -116,6 +120,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         );
     }
 
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
@@ -130,7 +138,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
     }
 
-    rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this.props.width - 20));
+    rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
     rtfHeight = () => (!this.selectedLayoutDoc ? 0 : this.rtfWidth() <= NumCast(this.selectedLayoutDoc?._width) ? Math.min(NumCast(this.selectedLayoutDoc?._height), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT);
 
     @action
@@ -138,8 +146,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const layoutDoc = this.selectedLayoutDoc;
         if (layoutDoc) {
             const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth);
-            if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20));
-            return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this.props.width - 20) : this.props.width - 20;
+            if (aspect) return Math.min(NumCast(layoutDoc._width), Math.min(this.MAX_EMBED_HEIGHT * aspect, this._props.width - 20));
+            return Doc.NativeWidth(layoutDoc) ? Math.min(NumCast(layoutDoc._width), this._props.width - 20) : this._props.width - 20;
         }
         return 0;
     };
@@ -155,10 +163,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     Doc.NativeAspect(layoutDoc, undefined, true)
                         ? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true)
                         : layoutDoc._layout_fitWidth
-                        ? !Doc.NativeHeight(this.dataDoc)
-                            ? NumCast(this.props.height)
-                            : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
-                        : NumCast(layoutDoc._height) || 50
+                          ? !Doc.NativeHeight(this.dataDoc)
+                              ? NumCast(this._props.height)
+                              : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this._props.height))
+                          : NumCast(layoutDoc._height) || 50
                 )
             );
         }
@@ -254,12 +262,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     };
 
     @computed get contexts() {
-        return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this.props.addDocTab} />;
+        return !this.selectedDoc ? null : <PropertiesDocContextSelector DocView={this.selectedDocumentView} hideTitle={true} addDocTab={this._props.addDocTab} />;
     }
 
     @computed get contextCount() {
         if (this.selectedDocumentView) {
-            const target = this.selectedDocumentView.props.Document;
+            const target = this.selectedDocumentView.Document;
             const embeddings = DocListCast(target.proto_embeddings);
             return embeddings.length - 1;
         } else {
@@ -269,7 +277,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @computed get links() {
         const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
-        return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this.props.addDocTab} />;
+        return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
     }
 
     @computed get linkCount() {
@@ -377,7 +385,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     color={SettingsManager.userColor}
                     onClick={action(() => {
                         if (this.selectedDocumentView || this.selectedDoc) {
-                            SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
+                            SharingManager.Instance.open(this.selectedDocumentView?.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc);
                         }
                     })}
                 />
@@ -619,7 +627,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @undoBatch
-    @action
     setTitle = (value: string | number) => {
         if (SelectionManager.Views().length > 1) {
             SelectionManager.Views().map(dv => Doc.SetInPlace(dv.Document, 'title', value, true));
@@ -630,7 +637,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     };
 
     @undoBatch
-    @action
     rotate = (angle: number) => {
         const _centerPoints: { X: number; Y: number }[] = [];
         const doc = this.selectedDoc;
@@ -1057,7 +1063,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @undoBatch
-    @action
     changeDash = () => {
         this.dashdStk = this.dashdStk === '2' ? '0' : '2';
     };
@@ -1676,8 +1681,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
         if (!this.selectedDoc && !this.isPres) {
             return (
-                <div className="propertiesView" style={{ width: this.props.width }}>
-                    <div className="propertiesView-title" style={{ width: this.props.width }}>
+                <div className="propertiesView" style={{ width: this._props.width }}>
+                    <div className="propertiesView-title" style={{ width: this._props.width }}>
                         No Document Selected
                     </div>
                 </div>
@@ -1690,11 +1695,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         style={{
                             background: SettingsManager.userBackgroundColor,
                             color: SettingsManager.userColor,
-                            width: this.props.width,
-                            minWidth: this.props.width,
+                            width: this._props.width,
+                            minWidth: this._props.width,
                         }}>
                         <div className="propertiesView-propAndInfoGrouping">
-                            <div className="propertiesView-title" style={{ width: this.props.width }}>
+                            <div className="propertiesView-title" style={{ width: this._props.width }}>
                                 Properties
                                 <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
                                     <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
@@ -1722,8 +1727,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as any as DocumentType)
                     : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type;
                 return (
-                    <div className="propertiesView" style={{ width: this.props.width }}>
-                        <div className="propertiesView-sectionTitle" style={{ width: this.props.width }}>
+                    <div className="propertiesView" style={{ width: this._props.width }}>
+                        <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
                             Presentation
                         </div>
                         <div className="propertiesView-name" style={{ borderBottom: 0 }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 4a08081a1..f3d515364 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -265,7 +265,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         }
         case StyleProp.PointerEvents:
             if (StrCast(doc?.pointerEvents) && !docProps?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
-            if (docProps?.DocumentView?.().props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
+            if (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
             if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (props?.pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
@@ -302,7 +302,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                             setSelectedVal={
                                 action((dv) => {
                                     (dv as any).select(false);
-                                    (SettingsManager.propertiesWidth = 250);
+                                    (SettingsManager.Instance.propertiesWidth = 250);
                                     setTimeout(action(() => {
                                         if (PropertiesView.Instance) {
                                             PropertiesView.Instance.CloseAll();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 858090c05..4ddf9e69b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
 import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, incrementTitleCopy } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -36,7 +36,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
-    @observable public static Instance: CollectionDockingView | undefined;
+    @observable public static Instance: CollectionDockingView | undefined = undefined;
     public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
         return {
             type: 'react-component',
@@ -62,9 +62,14 @@ export class CollectionDockingView extends CollectionSubView() {
     }
     private _goldenLayout: any = null;
     static _highlightStyleSheet: any = addStyleSheet();
+
+    _prevProps: SubCollectionViewProps;
+    @override _props: SubCollectionViewProps;
     constructor(props: SubCollectionViewProps) {
         super(props);
-        if (this.props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        if (this._props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
@@ -72,6 +77,10 @@ export class CollectionDockingView extends CollectionSubView() {
         this.Document.myTrails; // this is equivalent to having a prefetchProxy for myTrails which is needed for the My Trails button in the UI which assumes that Doc.ActiveDashboard.myTrails is legit...
     }
 
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     /**
      * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked.
      *
@@ -274,8 +283,8 @@ export class CollectionDockingView extends CollectionSubView() {
         return true;
     }
     setupGoldenLayout = async () => {
-        //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document)));
-        const config = StrCast(this.props.Document.dockingConfig);
+        //const config = StrCast(this._props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this._props.Document)));
+        const config = StrCast(this._props.Document.dockingConfig);
         if (config) {
             const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g);
             const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? [];
@@ -318,7 +327,7 @@ export class CollectionDockingView extends CollectionSubView() {
             );
             new _global.ResizeObserver(this.onResize).observe(this._containerRef.current);
             this._reactionDisposer = reaction(
-                () => StrCast(this.props.Document.dockingConfig),
+                () => StrCast(this._props.Document.dockingConfig),
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
                         //  bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
@@ -328,7 +337,7 @@ export class CollectionDockingView extends CollectionSubView() {
                 }
             );
             reaction(
-                () => this.props.PanelWidth(),
+                () => this._props.PanelWidth(),
                 width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
                 { fireImmediately: true }
             );
@@ -375,7 +384,7 @@ export class CollectionDockingView extends CollectionSubView() {
                   .map(id => DocServer.GetCachedRefField(id))
                   .filter(f => f)
                   .map(f => f as Doc);
-        const changesMade = this.props.Document.dockingConfig !== json;
+        const changesMade = this._props.Document.dockingConfig !== json;
         if (changesMade) {
             if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
                 this.layoutDoc.dockingConfig = json;
@@ -425,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     public CaptureThumbnail() {
-        const content = this.props.DocumentView?.()?.ContentDiv;
+        const content = this._props.DocumentView?.()?.ContentDiv;
         if (content) {
             const _width = Number(getComputedStyle(content).width.replace('px', ''));
             const _height = Number(getComputedStyle(content).height.replace('px', ''));
@@ -472,7 +481,7 @@ export class CollectionDockingView extends CollectionSubView() {
     stateChanged = () => {
         this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
         const json = JSON.stringify(this._goldenLayout.toConfig());
-        const changesMade = this.props.Document.dockingConfig !== json;
+        const changesMade = this._props.Document.dockingConfig !== json;
         return changesMade;
     };
 
@@ -506,8 +515,8 @@ export class CollectionDockingView extends CollectionSubView() {
             if (dashboard && e.target === stack.header?.element[0] && e.button === 2) {
                 dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
                 const docToAdd = Docs.Create.FreeformDocument([], {
-                    _width: this.props.PanelWidth(),
-                    _height: this.props.PanelHeight(),
+                    _width: this._props.PanelWidth(),
+                    _height: this._props.PanelHeight(),
                     _freeform_backgroundGrid: true,
                     _layout_fitWidth: true,
                     title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
@@ -524,8 +533,8 @@ export class CollectionDockingView extends CollectionSubView() {
                 if (dashboard) {
                     dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
                     const docToAdd = Docs.Create.FreeformDocument([], {
-                        _width: this.props.PanelWidth(),
-                        _height: this.props.PanelHeight(),
+                        _width: this._props.PanelWidth(),
+                        _height: this._props.PanelHeight(),
                         _layout_fitWidth: true,
                         _freeform_backgroundGrid: true,
                         title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
@@ -573,14 +582,14 @@ export class CollectionDockingView extends CollectionSubView() {
 
     render() {
         const href = ImageCast(this.Document.thumb)?.url?.href;
-        return this.props.renderDepth > -1 ? (
+        return this._props.renderDepth > -1 ? (
             <div>
                 {href ? (
                     <img
                         style={{ background: 'white', top: 0, position: 'absolute' }}
                         src={href} // + '?d=' + (new Date()).getTime()}
-                        width={this.props.PanelWidth()}
-                        height={this.props.PanelHeight()}
+                        width={this._props.PanelWidth()}
+                        height={this._props.PanelHeight()}
                     />
                 ) : (
                     <p>nested dashboards has no thumbnail</p>
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 61a5738ec..1c6f97c14 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,10 +1,9 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
-import { Id } from '../../../fields/FieldSymbols';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
@@ -49,14 +48,20 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @observable private color: string = '#f1efeb';
     @observable private collapsed: boolean = false;
     @observable private _paletteOn = false;
+    @observable _props: React.PropsWithChildren<CMVFieldRowProps>;
+    constructor(props: React.PropsWithChildren<CMVFieldRowProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
     private set _heading(value: string) {
-        runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value));
+        runInAction(() => this._props.headingObject && (this._props.headingObject.heading = this.heading = value));
     }
     private set _color(value: string) {
-        runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value));
+        runInAction(() => this._props.headingObject && (this._props.headingObject.color = this.color = value));
     }
     private set _collapsed(value: boolean) {
-        runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value));
+        runInAction(() => this._props.headingObject && (this._props.headingObject.collapsed = this.collapsed = value));
     }
 
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -68,28 +73,28 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         this._dropDisposer?.();
         if (ele) {
             this._ele = ele;
-            this.props.observeHeight(ele);
+            this._props.observeHeight(ele);
             this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this));
         }
     };
     @action
     componentDidMount() {
-        this.heading = this.props.headingObject?.heading || '';
-        this.color = this.props.headingObject?.color || '#f1efeb';
-        this.collapsed = this.props.headingObject?.collapsed || false;
+        this.heading = this._props.headingObject?.heading || '';
+        this.color = this._props.headingObject?.color || '#f1efeb';
+        this.collapsed = this._props.headingObject?.collapsed || false;
     }
     componentWillUnmount() {
-        this.props.unobserveHeight(this._ele);
+        this._props.unobserveHeight(this._ele);
     }
 
     getTrueHeight = () => {
         if (this.collapsed) {
-            this.props.setDocHeight(this.heading, 20);
+            this._props.setDocHeight(this.heading, 20);
         } else {
             const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header
-            const transformScale = this.props.screenToLocalTransform().Scale;
+            const transformScale = this._props.screenToLocalTransform().Scale;
             const trueHeight = rawHeight * transformScale;
-            this.props.setDocHeight(this.heading, trueHeight);
+            this._props.setDocHeight(this.heading, trueHeight);
         }
     };
 
@@ -97,10 +102,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     rowDrop = action((e: Event, de: DragManager.DropEvent) => {
         this._createEmbeddingSelected = false;
         if (de.complete.docDragData) {
-            const key = this.props.pivotField;
+            const key = this._props.pivotField;
             const castedValue = this.getValue(this.heading);
             const onLayoutDoc = this.onLayoutDoc(key);
-            if (this.props.parent.onInternalDrop(e, de)) {
+            if (this._props.parent.onInternalDrop(e, de)) {
                 de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc));
             }
             return true;
@@ -119,15 +124,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     headingChanged = (value: string, shiftDown?: boolean) => {
         this._createEmbeddingSelected = false;
-        const key = this.props.pivotField;
+        const key = this._props.pivotField;
         const castedValue = this.getValue(value);
         if (castedValue) {
-            if (this.props.parent.colHeaderData) {
-                if (this.props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
+            if (this._props.parent.colHeaderData) {
+                if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) {
                     return false;
                 }
             }
-            this.props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
+            this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true));
             this._heading = castedValue.toString();
             return true;
         }
@@ -152,24 +157,24 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
         if (!value && !forceEmptyNote) return false;
         this._createEmbeddingSelected = false;
-        const key = this.props.pivotField;
+        const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value });
         const onLayoutDoc = this.onLayoutDoc(key);
         FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = value;
-        (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this.props.heading);
-        const docs = this.props.parent.childDocList;
-        return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this.props.parent.props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
+        (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading);
+        const docs = this._props.parent.childDocList;
+        return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list)
     };
 
     deleteRow = undoBatch(
         action(() => {
             this._createEmbeddingSelected = false;
-            const key = this.props.pivotField;
-            this.props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
-            if (this.props.parent.colHeaderData && this.props.headingObject) {
-                const index = this.props.parent.colHeaderData.indexOf(this.props.headingObject);
-                this.props.parent.colHeaderData.splice(index, 1);
+            const key = this._props.pivotField;
+            this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true));
+            if (this._props.parent.colHeaderData && this._props.headingObject) {
+                const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject);
+                this._props.parent.colHeaderData.splice(index, 1);
             }
         })
     );
@@ -182,8 +187,8 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     };
 
     headerMove = (e: PointerEvent) => {
-        const embedding = Doc.MakeEmbedding(this.props.Document);
-        const key = this.props.pivotField;
+        const embedding = Doc.MakeEmbedding(this._props.Document);
+        const key = this._props.pivotField;
         let value = this.getValue(this.heading);
         value = typeof value === 'string' ? `"${value}"` : value;
         const script = `return doc.${key} === ${value}`;
@@ -198,7 +203,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         if (e.button === 0 && !e.ctrlKey) {
-            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeHidden && this.collapseSection(e));
+            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this._props.chromeHidden && this.collapseSection(e));
             this._createEmbeddingSelected = false;
         }
     };
@@ -207,7 +212,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
      * Returns true if a key is on the layout doc of the documents in the collection.
      */
     onLayoutDoc = (key: string): boolean => {
-        DocListCast(this.props.parent.Document.data).forEach(doc => {
+        DocListCast(this._props.parent.Document.data).forEach(doc => {
             if (Doc.Get(doc, key, true)) return true;
         });
         return false;
@@ -264,19 +269,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     };
 
     @computed get contentLayout() {
-        const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
-        const showChrome = !this.props.chromeHidden;
-        const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
+        const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor((this._props.parent._props.PanelWidth() - 2 * this._props.parent.xMargin) / (this._props.parent.columnWidth + this._props.parent.gridGap))));
+        const showChrome = !this._props.chromeHidden;
+        const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `;
         return this.collapsed ? null : (
             <div style={{ position: 'relative' }}>
-                {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : null}
+                {this._props.showHandle && this._props.parent._props.isContentActive() ? this._props.parent.columnDragger : null}
                 {showChrome ? (
                     <div
                         className="collectionStackingView-addDocumentButton"
                         style={
                             {
                                 //width: style.columnWidth / style.numGroupColumns,
-                                //padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`,
+                                //padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`,
                             }
                         }>
                         <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents={'+ NEW'} />
@@ -287,25 +292,25 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
                     ref={this._contRef}
                     style={{
                         padding: stackPad,
-                        minHeight: this.props.showHandle && this.props.parent.props.isContentActive() ? '10px' : undefined,
-                        width: this.props.parent.NodeWidth,
-                        gridGap: this.props.parent.gridGap,
-                        gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ''),
+                        minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined,
+                        width: this._props.parent.NodeWidth,
+                        gridGap: this._props.parent.gridGap,
+                        gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this._props.parent.columnWidth}px`, ''),
                     }}>
-                    {this.props.parent.children(this.props.docList)}
+                    {this._props.parent.children(this._props.docList)}
                 </div>
             </div>
         );
     }
 
     @computed get headingView() {
-        const noChrome = this.props.chromeHidden;
-        const key = this.props.pivotField;
-        const evContents = this.heading ? this.heading : this.props.type && this.props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
+        const noChrome = this._props.chromeHidden;
+        const key = this._props.pivotField;
+        const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`;
         const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine={true} />;
-        return this.props.Document.miniHeaders ? (
+        return this._props.Document.miniHeaders ? (
             <div className="collectionStackingView-miniHeader">{editableHeaderView}</div>
-        ) : !this.props.headingObject ? null : (
+        ) : !this._props.headingObject ? null : (
             <div className="collectionStackingView-sectionHeader" ref={this._headerRef}>
                 <div
                     className="collectionStackingView-sectionHeader-subCont"
@@ -352,7 +357,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     render() {
         const background = this._background;
         return (
-            <div className="collectionStackingView-masonrySection" style={{ width: this.props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
+            <div className="collectionStackingView-masonrySection" style={{ width: this._props.parent.NodeWidth, background }} ref={this.createRowDropRef} onPointerEnter={this.pointerEnteredRow} onPointerLeave={this.pointerLeaveRow}>
                 {this.headingView}
                 {this.contentLayout}
             </div>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 3edf4135f..f8e80ed87 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -33,7 +33,7 @@ interface CollectionMenuProps {
 export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     @observable static Instance: CollectionMenu;
 
-    @observable SelectedCollection: DocumentView | undefined;
+    @observable SelectedCollection: DocumentView | undefined = undefined;
     @observable FieldKey: string;
 
     private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -41,7 +41,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     constructor(props: any) {
         super(props);
         this.FieldKey = '';
-        runInAction(() => (CollectionMenu.Instance = this));
+        CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
         runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true)));
         this.jumpTo(300, 300);
@@ -69,19 +69,19 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     @action
     toggleTopBar = () => {
-        if (SettingsManager.headerBarHeight > 0) {
-            SettingsManager.headerBarHeight = 0;
+        if (SettingsManager.Instance.headerBarHeight > 0) {
+            SettingsManager.Instance.headerBarHeight = 0;
         } else {
-            SettingsManager.headerBarHeight = 60;
+            SettingsManager.Instance.headerBarHeight = 60;
         }
     };
 
     @action
     toggleProperties = () => {
         if (MainView.Instance.propertiesWidth() > 0) {
-            SettingsManager.propertiesWidth = 0;
+            SettingsManager.Instance.propertiesWidth = 0;
         } else {
-            SettingsManager.propertiesWidth = 300;
+            SettingsManager.Instance.propertiesWidth = 300;
         }
     };
 
@@ -94,7 +94,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     @computed get contMenuButtons() {
         const selDoc = Doc.MyContextMenuBtns;
         return !(selDoc instanceof Doc) ? null : (
-            <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}>
+            <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this._props.panelHeight() }}>
                 <CollectionLinearView
                     Document={selDoc}
                     fieldKey="data"
@@ -113,8 +113,8 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     pinToPres={emptyFunction}
                     removeDocument={returnFalse}
                     ScreenToLocalTransform={this.buttonBarXf}
-                    PanelWidth={this.props.panelWidth}
-                    PanelHeight={this.props.panelHeight}
+                    PanelWidth={this._props.panelWidth}
+                    PanelHeight={this._props.panelHeight}
                     renderDepth={0}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={emptyFunction}
@@ -127,10 +127,10 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     }
 
     render() {
-        const headerIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
-        const headerTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
-        const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
-        const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
+        const headerIcon = SettingsManager.Instance.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down';
+        const headerTitle = SettingsManager.Instance.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar';
+        const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties';
 
         const hardCodedButtons = (
             <div className={`hardCodedButtons`}>
@@ -139,7 +139,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     type={Type.PRIM}
                     color={SettingsManager.userColor}
                     onClick={this.toggleTopBar}
-                    toggleStatus={SettingsManager.headerBarHeight > 0}
+                    toggleStatus={SettingsManager.Instance.headerBarHeight > 0}
                     icon={<FontAwesomeIcon icon={headerIcon} size="lg" />}
                     tooltip={headerTitle}
                 />
@@ -148,7 +148,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
                     type={Type.PRIM}
                     color={SettingsManager.userColor}
                     onClick={this.toggleProperties}
-                    toggleStatus={SettingsManager.propertiesWidth > 0}
+                    toggleStatus={SettingsManager.Instance.propertiesWidth > 0}
                     icon={<FontAwesomeIcon icon={propIcon} size="lg" />}
                     tooltip={propTitle}
                 />
@@ -185,7 +185,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
 
     get document() {
-        return this.props.docView?.props.Document;
+        return this.props.docView?.Document;
     }
     get target() {
         return this.document;
@@ -577,13 +577,18 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
         return this.props.docView.props.Document;
     }
 
+    @computed get panelWidth() {
+        return this.props.docView.props.PanelWidth();
+    }
+
     componentDidMount() {
         runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700));
 
         // listener to reduce text on chrome resize (panel resize)
-        this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
-            runInAction(() => (this.resize = newValue < 700));
-        });
+        this.resizeListenerDisposer = reaction(
+            () => this.panelWidth,
+            newValue => (this.resize = newValue < 700)
+        );
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index ba00c4fa0..f9d99f1e5 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -416,7 +416,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // onInternalDrop is used when dragging and dropping a document within the view, such as dragging
     // a document to a new column or changing its order within the column.
     @undoBatch
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
             if (super.onInternalDrop(e, de)) {
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index c5677e2ba..a0f8b9c89 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -43,7 +43,7 @@ export class CollectionPileView extends CollectionSubView() {
     removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
         (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
         const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
-        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.Document);
+        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.()._props.removeDocument?.(this.Document);
         return ret;
     };
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 92b5470ae..28d15be71 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
+import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -20,9 +20,9 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { AudioWaveform } from '../AudioWaveform';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
+import { AudioWaveform } from '../nodes/audio/AudioWaveform';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
@@ -53,17 +53,28 @@ export enum TrimScope {
 }
 
 @observer
-export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+export class CollectionStackedTimeline extends CollectionSubView<SubCollectionViewProps & CollectionStackedTimelineProps>() {
     @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
     @observable public static CurrentlyPlaying: DocumentView[];
 
+    _prevProps: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+    @override _props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+    constructor(props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     static LabelScript: ScriptField;
     static LabelPlayScript: ScriptField;
 
     private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
     private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
     private _markerStart: number = 0;
-    @observable _markerEnd: number | undefined;
+    @observable _markerEnd: number | undefined = undefined;
     @observable _trimming: number = TrimScope.None;
     @observable _trimStart: number = 0; // trim controls start pos
     @observable _trimEnd: number = 0; // trim controls end pos
@@ -73,14 +84,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     @observable _hoverTime: number = 0;
 
-    @observable _thumbnail: string | undefined;
+    @observable _thumbnail: string | undefined = undefined;
 
     // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore
     get minTrimLength() {
         return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
     }
     @computed get thumbnails() {
-        return this.props.thumbnails?.();
+        return this._props.thumbnails?.();
     }
 
     @computed get trimStart() {
@@ -100,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         return this.clipEnd - this.clipStart;
     }
     @computed get clipEnd() {
-        return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
+        return this.IsTrimming === TrimScope.All ? this._props.rawDuration : NumCast(this.layoutDoc.clipEnd, this._props.rawDuration);
     }
 
     @computed get currentTime() {
@@ -115,11 +126,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         document.addEventListener('keydown', this.keyEvents, true);
     }
 
-    @action
     componentWillUnmount() {
         document.removeEventListener('keydown', this.keyEvents, true);
         if (CollectionStackedTimeline.SelectingRegion === this) {
-            CollectionStackedTimeline.SelectingRegion = undefined;
+            runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined));
         }
     }
 
@@ -149,8 +159,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         this._zoomFactor = zoom;
     }
 
-    anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
-    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null);
+    anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag]));
+    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
 
     // converts screen pixel offset to time
     toTimeline = (screen_delta: number, width: number) => {
@@ -177,13 +187,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         if (
             // need to include range inputs because after dragging video time slider it becomes target element
             !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
-            this.props.isContentActive()
+            this._props.isContentActive()
         ) {
             // if shift pressed scrub 1 second otherwise 1/10th
             const jump = e.shiftKey ? 1 : 0.1;
             switch (e.key) {
                 case ' ':
-                    this.props.playing() ? this.props.Pause() : this.props.Play();
+                    this._props.playing() ? this._props.Pause() : this._props.Play();
                     break;
                 case '^':
                     if (!CollectionStackedTimeline.SelectingRegion) {
@@ -191,7 +201,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         CollectionStackedTimeline.SelectingRegion = this;
                     } else {
                         this._markerEnd = this.currentTime;
-                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
                         this._markerEnd = undefined;
                         CollectionStackedTimeline.SelectingRegion = undefined;
                     }
@@ -204,11 +214,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._trimming = TrimScope.None;
                     break;
                 case 'ArrowLeft':
-                    this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+                    this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
                     e.stopPropagation();
                     break;
                 case 'ArrowRight':
-                    this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+                    this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
                     e.stopPropagation();
                     break;
             }
@@ -218,7 +228,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     getLinkData(l: Doc) {
         let la1 = l.link_anchor_1 as Doc;
         let la2 = l.link_anchor_2 as Doc;
-        const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+        const linkTime = NumCast(la2[this._props.startTag], NumCast(la1[this._props.startTag]));
         if (Doc.AreProtosEqual(la1, this.dataDoc)) {
             la1 = l.link_anchor_2 as Doc;
             la2 = l.link_anchor_1 as Doc;
@@ -233,9 +243,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const clientX = e.clientX;
         const diff = rect ? clientX - rect?.x : null;
         const shiftKey = e.shiftKey;
-        if (rect && this.props.isContentActive()) {
-            const wasPlaying = this.props.playing();
-            if (wasPlaying) this.props.Pause();
+        if (rect && this._props.isContentActive()) {
+            const wasPlaying = this._props.playing();
+            if (wasPlaying) this._props.Pause();
             var wasSelecting = this._markerEnd !== undefined;
             setupMoveUpEvents(
                 this,
@@ -257,7 +267,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         this._markerEnd = tmp;
                     }
                     if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
-                        const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+                        const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
                         setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
                     }
                     (!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -265,17 +275,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 }),
                 (e, doubleTap) => {
                     if (e.button !== 2) {
-                        this.props.select(false);
-                        !wasPlaying && doubleTap && this.props.Play();
+                        this._props.select(false);
+                        !wasPlaying && doubleTap && this._props.Play();
                     }
                 },
-                this.props.isSelected() || this.props.isContentActive(),
+                this._props.isSelected() || this._props.isContentActive(),
                 undefined,
                 () => {
                     if (shiftKey) {
-                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
+                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this.currentTime, undefined, undefined, true);
                     } else {
-                        !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+                        !wasPlaying && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
                     }
                 }
             );
@@ -290,7 +300,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         if (rect) {
             this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
             if (this.thumbnails) {
-                const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
+                const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails);
                 const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined;
                 this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
             }
@@ -305,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             this,
             e,
             action((e, [], []) => {
-                if (rect && this.props.isContentActive()) {
+                if (rect && this._props.isContentActive()) {
                     this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
                 }
                 return false;
@@ -325,7 +335,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             this,
             e,
             action((e, [], []) => {
-                if (rect && this.props.isContentActive()) {
+                if (rect && this._props.isContentActive()) {
                     this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
                 }
                 return false;
@@ -348,8 +358,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     @action
     scrollToTime = (time: number) => {
         if (this._timelineWrapper) {
-            if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
-                this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+            if (time > this.toTimeline(this._scroll + this._props.PanelWidth(), this.timelineContentWidth)) {
+                this._scroll = Math.min(this._scroll + this._props.PanelWidth(), this.timelineContentWidth - this._props.PanelWidth());
                 smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
             } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
                 this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
@@ -363,15 +373,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
         if (super.onInternalDrop(e, de)) {
             // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
-            const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+            const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
             const x = localPt[0] - docDragData.offset[0];
             const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
             docDragData.droppedDocuments.forEach(drop => {
                 const anchorEnd = this.anchorEnd(drop);
                 if (anchorEnd !== undefined) {
-                    Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+                    Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this._props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
                 }
-                Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+                Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this._props.startTag : 'timecodeToShow', timelinePt, false);
             });
 
             return true;
@@ -386,7 +396,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     // creates marker on timeline
     @undoBatch
-    @action
     static createAnchor(doc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
         if (anchorStartTime === undefined) return doc;
         const startTag = '_timecodeToShow';
@@ -422,20 +431,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
         if (this.layoutDoc.autoPlayAnchors) {
-            if (this.props.playing()) this.props.Pause();
+            if (this._props.playing()) this._props.Pause();
             else {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
                 this.scrollToTime(seekTimeInSeconds);
             }
         } else {
             if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
-                if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
-                    this.props.Pause();
+                if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+                    this._props.Pause();
                 } else {
-                    this.props.Play();
+                    this._props.Play();
                 }
             } else {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
                 this.scrollToTime(seekTimeInSeconds);
             }
         }
@@ -450,17 +459,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
         if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) {
-            if (this.props.playing()) this.props.Pause();
-            else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
+            if (this._props.playing()) this._props.Pause();
+            else if (this.layoutDoc.autoPlayAnchors) this._props.Play();
             else if (!this.layoutDoc.autoPlayAnchors) {
                 const rect = this._timeline?.getBoundingClientRect();
-                rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+                rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
             }
         } else {
             if (this.layoutDoc.autoPlayAnchors) {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
             } else {
-                this.props.setTime(seekTimeInSeconds);
+                this._props.setTime(seekTimeInSeconds);
             }
         }
         return { select: true };
@@ -490,18 +499,18 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     };
 
     dictationHeightPercent = 50;
-    dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+    dictationHeight = () => (this._props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
 
     @computed get timelineContentHeight() {
-        return (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+        return (this._props.PanelHeight() * this.dictationHeightPercent) / 100;
     }
     @computed get timelineContentWidth() {
-        return this.props.PanelWidth() * this.zoomFactor;
+        return this._props.PanelWidth() * this.zoomFactor;
     } // subtract size of container border
 
-    dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+    dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive();
 
     currentTimecode = () => this.currentTime;
 
@@ -520,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     }
 
     @computed get timelineEvents() {
-        return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+        return this._props.isContentActive() ? 'all' : this._props.isContentActive() === false ? 'none' : undefined;
     }
     render() {
         const overlaps: {
@@ -537,7 +546,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
                 <div
                     className="collectionStackedTimeline-timelineContainer"
-                    style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
+                    style={{ width: this._props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
                     onWheel={e => this.isContentActive() && e.stopPropagation()}
                     onScroll={this.setScroll}
                     onMouseMove={e => this.isContentActive() && this.onHover(e)}
@@ -553,11 +562,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
                             if (end < this.clipStart || start > this.clipEnd) return null;
                             const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
-                            const top = (d.level / maxLevel) * this.props.PanelHeight();
+                            const top = (d.level / maxLevel) * this._props.PanelHeight();
                             const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
                             const width = (timespan / this.clipDuration) * this.timelineContentWidth;
-                            const height = this.props.PanelHeight() / maxLevel;
-                            return this.props.Document.hideAnchors ? null : (
+                            const height = this._props.PanelHeight() / maxLevel;
+                            return this._props.Document.hideAnchors ? null : (
                                 <div
                                     className={'collectionStackedTimeline-marker-timeline'}
                                     key={d.anchor[Id]}
@@ -569,7 +578,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                         pointerEvents: 'none',
                                     }}>
                                     <StackedTimelineAnchor
-                                        {...this.props}
+                                        {...this._props}
                                         mark={d.anchor}
                                         rangeClickScript={this.rangeClickScript}
                                         rangePlayScript={this.rangePlayScript}
@@ -590,18 +599,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             );
                         })}
                         {!this.IsTrimming && this.selectionContainer}
-                        {!this.props.PanelHeight() ? null : (
+                        {!this._props.PanelHeight() ? null : (
                             <AudioWaveform
-                                rawDuration={this.props.rawDuration}
-                                fieldKey={this.props.dataFieldKey}
+                                rawDuration={this._props.rawDuration}
+                                fieldKey={this._props.dataFieldKey}
                                 duration={this.clipDuration}
-                                mediaPath={this.props.mediaPath}
+                                mediaPath={this._props.mediaPath}
                                 layoutDoc={this.layoutDoc}
                                 clipStart={this.clipStart}
                                 clipEnd={this.clipEnd}
                                 zoomFactor={this.zoomFactor}
                                 PanelHeight={this.timelineContentHeight}
                                 PanelWidth={this.timelineContentWidth}
+                                progress={(this.currentTime - this.clipStart) / this.clipDuration}
                             />
                         )}
 
@@ -691,37 +701,44 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    constructor(props: any) {
+    _prevProps: React.PropsWithChildren<StackedTimelineAnchorProps>;
+    @observable _props: React.PropsWithChildren<StackedTimelineAnchorProps>;
+    constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) {
         super(props);
-        this._lastTimecode = this.props.currentTimecode();
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._lastTimecode = this._props.currentTimecode();
+    }
+    componentDidUpdate() {
+        copyProps(this);
     }
 
     // updates marker document title to reflect correct timecodes
     computeTitle = () => {
-        if (this.props.mark.type !== DocumentType.LABEL) return undefined;
-        const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
-        const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+        if (this._props.mark.type !== DocumentType.LABEL) return undefined;
+        const start = Math.max(NumCast(this._props.mark[this._props.startTag]), this._props.trimStart) - this._props.trimStart;
+        const end = Math.min(NumCast(this._props.mark[this._props.endTag]), this._props.trimEnd) - this._props.trimStart;
         return `#${formatTime(start)}-${formatTime(end)}`;
     };
 
     componentDidMount() {
         this._disposer = reaction(
-            () => this.props.currentTimecode(),
+            () => this._props.currentTimecode(),
             time => {
-                const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null);
-                const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
+                const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
+                const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
                 if (
                     !LightboxView.LightboxDoc &&
                     // bcz: when should links be followed?  we don't want to move away from the video to follow a link but we can open it in a sidebar/etc.  But we don't know that upfront.
                     // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
-                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
-                    !this.props.layoutDoc.dontAutoFollowLinks &&
-                    LinkManager.Links(this.props.mark).length &&
-                    time > NumCast(this.props.mark[this.props.startTag]) &&
-                    time < NumCast(this.props.mark[this.props.endTag]) &&
-                    this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
+                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+                    !this._props.layoutDoc.dontAutoFollowLinks &&
+                    LinkManager.Links(this._props.mark).length &&
+                    time > NumCast(this._props.mark[this._props.startTag]) &&
+                    time < NumCast(this._props.mark[this._props.endTag]) &&
+                    this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5
                 ) {
-                    LinkFollower.FollowLink(undefined, this.props.mark, false);
+                    LinkFollower.FollowLink(undefined, this._props.mark, false);
                 }
                 this._lastTimecode = time;
             }
@@ -736,16 +753,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // starting the drag event for anchor resizing
     @action
     onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
-        //this.props._timeline?.setPointerCapture(e.pointerId);
+        //this._props._timeline?.setPointerCapture(e.pointerId);
         const newTime = (e: PointerEvent) => {
             const rect = (e.target as any).getBoundingClientRect();
-            return this.props.toTimeline(e.clientX - rect.x, rect.width);
+            return this._props.toTimeline(e.clientX - rect.x, rect.width);
         };
         const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
-            const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined;
+            const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
             if (timelineOnly) {
-                if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined;
-                Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
+                if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
+                Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
                 if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
             } else {
                 anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
@@ -759,11 +776,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
             e,
             e => {
                 if (!undo) undo = UndoManager.StartBatch('drag anchor');
-                this.props.setTime(newTime(e));
+                this._props.setTime(newTime(e));
                 return changeAnchor(anchor, left, newTime(e));
             },
             action(e => {
-                this.props.setTime(newTime(e));
+                this._props.setTime(newTime(e));
                 undo?.end();
                 this.noEvents = false;
             }),
@@ -774,7 +791,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // context menu
     contextMenuItems = () => {
         const resetTitle = {
-            script: ScriptField.MakeFunction(`this.title = this["${this.props.endTag}"] ? "#" + formatToTime(this["${this.props.startTag}"]) + "-" + formatToTime(this["${this.props.endTag}"]) : "#" + formatToTime(this["${this.props.startTag}"])`)!,
+            script: ScriptField.MakeFunction(
+                `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
+            )!,
             icon: 'folder-plus',
             label: 'Reset Title',
         };
@@ -785,7 +804,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as Opt<DocumentView> | null });
         const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
-            this.props.playLink(mark, options);
+            this._props.playLink(mark, options);
             return undefined;
         };
         return {
@@ -793,7 +812,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
             view: (
                 <DocumentView
                     key="view"
-                    {...this.props}
+                    {...this._props}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
@@ -801,11 +820,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     TemplateDataDocument={undefined}
                     docViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
-                    styleProvider={this.props.styleProvider}
-                    renderDepth={this.props.renderDepth + 1}
+                    styleProvider={this._props.styleProvider}
+                    renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
-                    isDocumentActive={this.props.isDocumentActive}
+                    isDocumentActive={this._props.isDocumentActive}
                     PanelWidth={width}
                     PanelHeight={height}
                     layout_fitWidth={returnTrue}
@@ -817,7 +836,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     onClick={script}
-                    onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+                    onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
                     hideResizeHandles={true}
                     bringToFront={emptyFunction}
@@ -827,19 +846,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
         };
     });
 
-    anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
-    width = () => this.props.width;
-    height = () => this.props.height;
+    anchorScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(-this._props.left, -this._props.top);
+    width = () => this._props.width;
+    height = () => this._props.height;
 
     render() {
-        const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
+        const inner = this.renderInner(this._props.mark, this._props.rangeClickScript, this._props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
         return (
             <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
                 {inner.view}
                 {!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
                     <>
-                        <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
-                        <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
+                        <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} />
+                        <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} />
                     </>
                 )}
             </div>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bc428c22f..984d6eedd 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as CSS from 'csstype';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -31,7 +31,7 @@ import { StyleProp } from '../StyleProvider';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -64,7 +64,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     // does this mean whether the browser is hidden? Or is chrome something else entirely?
     @computed get chromeHidden() {
-        return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
+        return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden);
     }
     // it looks like this gets the column headers that Mehek was showing just now
     @computed get colHeaderData() {
@@ -77,18 +77,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // filteredChildren is what you want to work with. It's the list of things that you're currently displaying
     @computed get filteredChildren() {
         const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout);
-        if (this.props.sortFunc) children.sort(this.props.sortFunc);
+        if (this._props.sortFunc) children.sort(this._props.sortFunc);
         return children;
     }
     // how much margin we give the header
     @computed get headerMargin() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin);
     }
     @computed get xMargin() {
-        return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this.props.PanelWidth()));
+        return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth()));
     }
     @computed get yMargin() {
-        return this.props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this.props.PanelWidth()));
+        return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth()));
     }
 
     @computed get gridGap() {
@@ -96,7 +96,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
     // are we stacking or masonry?
     @computed get isStackingView() {
-        return (this.props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
+        return (this._props.type_collection ?? this.layoutDoc._type_collection) === CollectionViewType.Stacking;
     }
     // this is the number of StackingViewFieldColumns that we have
     @computed get numGroupColumns() {
@@ -108,15 +108,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
     // columnWidth handles the margin on the left and right side of the documents
     @computed get columnWidth() {
-        return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
+        return Math.min(this._props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this._props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
     }
 
     @computed get NodeWidth() {
-        return this.props.PanelWidth() - this.gridGap;
+        return this._props.PanelWidth() - this.gridGap;
     }
 
+    _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>;
+    _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionStackingViewProps>>;
     constructor(props: any) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
 
         if (this.colHeaderData === undefined) {
             // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out?
@@ -124,6 +128,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
         }
     }
+    componentDidUpdate() {
+        copyProps(this);
+    }
 
     // TODO: plj - these are the children
     children = (docs: Doc[]) => {
@@ -203,7 +210,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     componentDidMount() {
         super.componentDidMount?.();
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
 
         // reset section headers when a new filter is inputted
         this._pivotFieldDisposer = reaction(
@@ -214,7 +221,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             () => this.layoutDoc._layout_autoHeight,
             layout_autoHeight =>
                 layout_autoHeight &&
-                this.props.setHeight?.(
+                this._props.setHeight?.(
                     Math.min(
                         NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
                         this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0))
@@ -229,20 +236,19 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this._layout_autoHeightDisposer?.();
     }
 
-    isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+    isAnyChildContentActive = () => this._props.isAnyChildContentActive();
 
-    @action
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
-        return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
+        return this._props.removeDocument?.(doc) && addDocument?.(doc) ? true : false;
     };
     createRef = (ele: HTMLDivElement | null) => {
         this._masonryGridRef = ele;
         this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
     };
 
-    onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+    onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
     @computed get onChildDoubleClickHandler() {
-        return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+        return () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     }
 
     scrollToBottom = () => {
@@ -256,7 +262,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
-            const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+            const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -268,17 +274,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc) {
-            if (this.props.childOpacity) {
-                return this.props.childOpacity();
+            if (this._props.childOpacity) {
+                return this._props.childOpacity();
             }
             if (this.Document._currentFrame !== undefined) {
                 return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity;
             }
         }
-        return this.props.styleProvider?.(doc, props, property);
+        return this._props.styleProvider?.(doc, props, property);
     };
     @undoBatch
-    @action
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         const docView = fieldProps.DocumentView?.();
         if (docView && ['Enter'].includes(e.key) && e.ctrlKey) {
@@ -296,26 +301,26 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             return this.addDocument?.(newDoc);
         }
     };
-    isContentActive = () => (this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined);
+    isContentActive = () => (this._props.isContentActive() ? true : this._props.isSelected() === false || this._props.isContentActive() === false ? false : undefined);
 
     @observable _renderCount = 5;
     isChildContentActive = () =>
-        this.props.isContentActive?.() === false
+        this._props.isContentActive?.() === false
             ? false
-            : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
-            ? true
-            : this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
-            ? false
-            : undefined;
-    isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
+            : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+              ? true
+              : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+                ? false
+                : undefined;
+    isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
-    childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this.props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
+    childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
     getDisplayDoc(doc: Doc, width: () => number, count: number) {
-        const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this.props.TemplateDataDocument : undefined;
+        const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
         const height = () => this.getDocHeight(doc);
-        const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this.props.PanelHeight()));
+        const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
         const panelWidth = () => (this.isStackingView ? width() : this.columnWidth);
         const stackedDocTransform = () => this.getDocTransform(doc);
         this._docXfs.push({ stackedDocTransform, width, height });
@@ -324,45 +329,45 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
                 Document={doc}
                 TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])}
-                renderDepth={this.props.renderDepth + 1}
+                renderDepth={this._props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
-                pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
+                pointerEvents={this._props.DocumentView?.()._props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
                 styleProvider={this.styleProvider}
-                docViewPath={this.props.docViewPath}
+                docViewPath={this._props.docViewPath}
                 layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
-                onBrowseClick={this.props.onBrowseClick}
+                onBrowseClick={this._props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
-                LayoutTemplate={this.props.childLayoutTemplate}
-                LayoutTemplateString={this.props.childLayoutString}
-                NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
-                NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
-                dontCenter={this.props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)}
-                dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+                NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
+                dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as any)}
+                dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
                 rootSelected={this.rootSelected}
-                layout_showTitle={this.props.childlayout_showTitle}
-                dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+                layout_showTitle={this._props.childlayout_showTitle}
+                dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={stackedDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
-                hideDecorationTitle={this.props.childHideDecorationTitle}
-                hideResizeHandles={this.props.childHideResizeHandles}
+                hideDecorationTitle={this._props.childHideDecorationTitle}
+                hideResizeHandles={this._props.childHideResizeHandles}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
-                yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
-                addDocument={this.props.addDocument}
-                moveDocument={this.props.moveDocument}
-                removeDocument={this.props.removeDocument}
+                xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
+                yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+                addDocument={this._props.addDocument}
+                moveDocument={this._props.moveDocument}
+                removeDocument={this._props.removeDocument}
                 contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents) as any}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                addDocTab={this._props.addDocTab}
                 bringToFront={returnFalse}
-                pinToPres={this.props.pinToPres}
+                pinToPres={this._props.pinToPres}
             />
         );
     }
@@ -372,11 +377,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
         const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale);
     }
     getDocWidth(d?: Doc) {
         if (!d) return 0;
-        const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
         if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
             return Math.min(NumCast(d._width), maxWidth);
@@ -385,9 +390,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
     getDocHeight(d?: Doc) {
         if (!d || d.hidden) return 0;
-        const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
-        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this.props.TemplateDataDocument;
-        const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
+        const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
+        const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
+        const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1));
         const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0);
         const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
         if (nw && nh) {
@@ -396,7 +401,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             return Math.min(maxHeight, (docWid * nh) / nw);
         }
         const childHeight = NumCast(childLayoutDoc._height);
-        const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
+        const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this._props.PanelHeight() - 2 * this.yMargin;
         return Math.min(childHeight, maxHeight, panelHeight);
     }
 
@@ -434,7 +439,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     }
 
     @undoBatch
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         // Fairly confident that this is where the swapping of nodes in the various arrays happens
         const where = [de.x, de.y];
@@ -472,9 +476,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 }
                 return true;
             }
-        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this.props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
+        } else if (de.complete.linkDragData?.dragDocument.embedContainer === this._props.Document && de.complete.linkDragData?.linkDragView?.CollectionFreeFormDocumentView) {
             const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _layout_fitWidth: true, title: 'dropped annotation' });
-            if (!this.props.addDocument?.(source)) e.preventDefault();
+            if (!this._props.addDocument?.(source)) e.preventDefault();
             de.complete.linkDocument = DocUtils.MakeLink(source, de.complete.linkDragData.linkSourceGetAnchor(), { link_relationship: 'doc annotation' }); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
@@ -497,7 +501,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     /// an item from outside of Dash is being dropped onto this stacking view (e.g, a document from the file system)
     @undoBatch
-    @action
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         const where = [e.clientX, e.clientY];
         let targInd = -1;
@@ -544,8 +547,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                                     const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))));
-                                    if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
-                                        this.props.setHeight?.(height);
+                                    if (!LightboxView.IsLightboxDocView(this._props.docViewPath())) {
+                                        this._props.setHeight?.(height);
                                     }
                                 }
                             })
@@ -556,8 +559,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 addDocument={this.addDocument}
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
-                Document={this.props.Document}
-                TemplateDataDocument={this.props.TemplateDataDocument}
+                Document={this._props.Document}
+                TemplateDataDocument={this._props.TemplateDataDocument}
                 renderChildren={this.children}
                 columnWidth={this.columnWidth}
                 numGroupColumns={this.numGroupColumns}
@@ -571,7 +574,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 yMargin={this.yMargin}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this.props.ScreenToLocalTransform}
+                screenToLocalTransform={this._props.ScreenToLocalTransform}
             />
         );
     };
@@ -584,11 +587,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
             type = types[0];
         }
-        const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
+        const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
         return (
             <CollectionMasonryViewFieldRow
                 showHandle={first}
-                Document={this.props.Document}
+                Document={this._props.Document}
                 chromeHidden={this.chromeHidden}
                 pivotField={this.pivotField}
                 unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)}
@@ -599,7 +602,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                             action((entries: any) => {
                                 if (this.layoutDoc._layout_autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
                                     const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
-                                    this.props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
+                                    this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
                                 }
                             })
                         );
@@ -615,7 +618,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 parent={this}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this.props.ScreenToLocalTransform}
+                screenToLocalTransform={this._props.ScreenToLocalTransform}
                 setDocHeight={this.setDocHeight}
             />
         );
@@ -672,43 +675,43 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                     Document={menuDoc}
                     isContentActive={this.isContentActive}
                     isDocumentActive={this.isContentActive}
-                    addDocument={this.props.addDocument}
-                    moveDocument={this.props.moveDocument}
-                    addDocTab={this.props.addDocTab}
-                    onBrowseClick={this.props.onBrowseClick}
+                    addDocument={this._props.addDocument}
+                    moveDocument={this._props.moveDocument}
+                    addDocTab={this._props.addDocTab}
+                    onBrowseClick={this._props.onBrowseClick}
                     pinToPres={emptyFunction}
                     rootSelected={this.rootSelected}
-                    removeDocument={this.props.removeDocument}
+                    removeDocument={this._props.removeDocument}
                     ScreenToLocalTransform={Transform.Identity}
                     PanelWidth={this.return35}
                     PanelHeight={this.return35}
-                    renderDepth={this.props.renderDepth}
+                    renderDepth={this._props.renderDepth}
                     focus={emptyFunction}
-                    styleProvider={this.props.styleProvider}
+                    styleProvider={this._props.styleProvider}
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    childFilters={this.props.childFilters}
-                    childFiltersByRanges={this.props.childFiltersByRanges}
-                    searchFilterDocs={this.props.searchFilterDocs}
+                    childFilters={this._props.childFilters}
+                    childFiltersByRanges={this._props.childFiltersByRanges}
+                    searchFilterDocs={this._props.searchFilterDocs}
                 />
             </div>
         );
     }
 
     @computed get nativeWidth() {
-        return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
+        return this._props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc);
     }
     @computed get nativeHeight() {
-        return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
+        return this._props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc);
     }
 
     @computed get scaling() {
-        return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight;
+        return !this.nativeWidth ? 1 : this._props.PanelHeight() / this.nativeHeight;
     }
 
     @computed get backgroundEvents() {
-        return this.props.isContentActive() === false ? 'none' : undefined;
+        return this._props.isContentActive() === false ? 'none' : undefined;
     }
     observer: any;
     render() {
@@ -734,8 +737,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         ref={this.createRef}
                         style={{
                             overflowY: this.isContentActive() ? 'auto' : 'hidden',
-                            background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor),
-                            pointerEvents: (this.props.pointerEvents?.() as any) ?? this.backgroundEvents,
+                            background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor),
+                            pointerEvents: (this._props.pointerEvents?.() as any) ?? this.backgroundEvents,
                         }}
                         onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
                         onDrop={this.onExternalDrop.bind(this)}
@@ -743,7 +746,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         onWheel={e => this.isContentActive() && e.stopPropagation()}>
                         {this.renderedSections}
                         {!this.showAddAGroup ? null : (
-                            <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+                            <div key={`${this._props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                                 <EditableView {...editableViewProps} />
                             </div>
                         )}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3282eb4b4..b8dcd6248 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -50,15 +50,22 @@ interface CSVFieldColumnProps {
 
 @observer
 export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
-    @observable private _background = 'inherit';
-
     private dropDisposer?: DragManager.DragDropDisposer;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
-
+    @observable private _background = 'inherit';
     @observable _paletteOn = false;
-    @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
-    @observable _color = this.props.headingObject ? this.props.headingObject.color : '#f1efeb';
+    @observable _heading = '';
+    @observable _color = '';
+
+    @observable _props!: CSVFieldColumnProps;
+    constructor(props: CSVFieldColumnProps) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+        this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
+        this._color = this._props.headingObject ? this._props.headingObject.color : '#f1efeb';
+    }
 
     _ele: HTMLElement | null = null;
 
@@ -68,29 +75,33 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         this.dropDisposer?.();
         if (ele) {
             this._ele = ele;
-            this.props.observeHeight(ele);
+            this._props.observeHeight(ele);
             this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this));
         }
     };
 
+    componentDidUpdate() {
+        // untracked(() => (this._props = this.props));
+    }
+
     @action
     componentDidMount() {
         this._disposers.collapser = reaction(
-            () => this.props.headingObject?.collapsed,
+            () => this._props.headingObject?.collapsed,
             collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
             { fireImmediately: true }
         );
     }
     componentWillUnmount() {
         this._disposers.collapser?.();
-        this.props.unobserveHeight(this._ele);
+        this._props.unobserveHeight(this._ele);
     }
 
     //TODO: what is scripting? I found it in SetInPlace def but don't know what that is
     @undoBatch
     columnDrop = action((e: Event, de: DragManager.DropEvent) => {
         const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) };
-        this.props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false));
+        this._props.pivotField && drop.docs?.forEach(d => Doc.SetInPlace(d, this._props.pivotField, drop.val, false));
         return true;
     });
     getValue = (value: string): any => {
@@ -105,13 +116,13 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     headingChanged = (value: string, shiftDown?: boolean) => {
         const castedValue = this.getValue(value);
         if (castedValue) {
-            if (this.props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
+            if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) {
                 return false;
             }
-            this.props.docList.forEach(d => (d[this.props.pivotField] = castedValue));
-            if (this.props.headingObject) {
-                this.props.headingObject.setHeading(castedValue.toString());
-                this._heading = this.props.headingObject.heading;
+            this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue));
+            if (this._props.headingObject) {
+                this._props.headingObject.setHeading(castedValue.toString());
+                this._heading = this._props.headingObject.heading;
             }
             return true;
         }
@@ -120,7 +131,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @action
     changeColumnColor = (color: string) => {
-        this.props.headingObject?.setColor(color);
+        this._props.headingObject?.setColor(color);
         this._color = color;
     };
 
@@ -131,30 +142,30 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     @action
     addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
         if (!value && !forceEmptyNote) return false;
-        const key = this.props.pivotField;
+        const key = this._props.pivotField;
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
-        newDoc[key] = this.getValue(this.props.heading);
-        const maxHeading = this.props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
-        const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
+        newDoc[key] = this.getValue(this._props.heading);
+        const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0);
+        const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
         newDoc.heading = heading;
         FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
-        return this.props.addDocument?.(newDoc) || false;
+        return this._props.addDocument?.(newDoc) || false;
     };
 
     @action
     deleteColumn = () => {
-        this.props.docList.forEach(d => (d[this.props.pivotField] = undefined));
-        if (this.props.colHeaderData && this.props.headingObject) {
-            const index = this.props.colHeaderData.indexOf(this.props.headingObject);
-            this.props.colHeaderData.splice(index, 1);
+        this._props.docList.forEach(d => (d[this._props.pivotField] = undefined));
+        if (this._props.colHeaderData && this._props.headingObject) {
+            const index = this._props.colHeaderData.indexOf(this._props.headingObject);
+            this._props.colHeaderData.splice(index, 1);
         }
     };
 
     @action
     collapseSection = () => {
-        this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed);
-        this.collapsed = BoolCast(this.props.headingObject?.collapsed);
+        this._props.headingObject?.setCollapsed(!this._props.headingObject.collapsed);
+        this.collapsed = BoolCast(this._props.headingObject?.collapsed);
     };
 
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
@@ -162,12 +173,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     //TODO: I think this is where I'm supposed to edit stuff
     startDrag = (e: PointerEvent, down: number[], delta: number[]) => {
         // is MakeEmbedding a way to make a copy of a doc without rendering it?
-        const embedding = Doc.MakeEmbedding(this.props.Document);
-        embedding._width = this.props.columnWidth / (this.props.colHeaderData?.length || 1);
+        const embedding = Doc.MakeEmbedding(this._props.Document);
+        embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1);
         embedding._pivotField = undefined;
         let value = this.getValue(this._heading);
         value = typeof value === 'string' ? `"${value}"` : value;
-        embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name });
+        embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name });
         if (embedding.viewSpecScript) {
             DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY);
             return true;
@@ -177,7 +188,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     renderColorPicker = () => {
         const gray = '#f1efeb';
-        const selected = this.props.headingObject ? this.props.headingObject.color : gray;
+        const selected = this._props.headingObject ? this._props.headingObject.color : gray;
         const colors = ['pink2', 'purple4', 'bluegreen1', 'yellow4', 'gray', 'red2', 'bluegreen7', 'bluegreen5', 'orange1'];
         return (
             <div className="collectionStackingView-colorPicker">
@@ -211,15 +222,15 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         ContextMenu.Instance.clearItems();
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
-        const dataDoc = this.props.TemplateDataDocument || this.props.Document;
+        const dataDoc = this._props.TemplateDataDocument || this._props.Document;
         const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
         const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
         DocUtils.addDocumentCreatorMenuItems(
             doc => {
                 FormattedTextBox.SetSelectOnLoad(doc);
-                return this.props.addDocument?.(doc);
+                return this._props.addDocument?.(doc);
             },
-            this.props.addDocument,
+            this._props.addDocument,
             0,
             0,
             true
@@ -231,12 +242,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 docItems.push({
                     description: ':' + fieldKey,
                     event: () => {
-                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document));
                         if (created) {
-                            if (this.props.Document.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+                            if (this._props.Document.isTemplateDoc) {
+                                Doc.MakeMetadataFieldTemplate(created, this._props.Document);
                             }
-                            return this.props.addDocument?.(created);
+                            return this._props.addDocument?.(created);
                         }
                     },
                     icon: 'compress-arrows-alt',
@@ -250,12 +261,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     event: () => {
                         const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
                         if (created) {
-                            const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+                            const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document;
                             if (container.isTemplateDoc) {
                                 Doc.MakeMetadataFieldTemplate(created, container);
                                 return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
                             }
-                            return this.props.addDocument?.(created) || false;
+                            return this._props.addDocument?.(created) || false;
                         }
                     },
                     icon: 'compress-arrows-alt',
@@ -264,16 +275,16 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
         !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
         ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
-            Doc.GetProto(this.props.Document)[name] = '';
+            Doc.GetProto(this._props.Document)[name] = '';
             const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true });
             if (created) {
-                if (this.props.Document.isTemplateDoc) {
-                    Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+                if (this._props.Document.isTemplateDoc) {
+                    Doc.MakeMetadataFieldTemplate(created, this._props.Document);
                 }
-                this.props.addDocument?.(created);
+                this._props.addDocument?.(created);
             }
         });
-        const pt = this.props
+        const pt = this._props
             .screenToLocalTransform()
             .inverse()
             .transformPoint(width - 30, height);
@@ -282,21 +293,21 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     @computed get innards() {
         TraceMobx();
-        const key = this.props.pivotField;
-        const headings = this.props.headings();
+        const key = this._props.pivotField;
+        const headings = this._props.headings();
         const heading = this._heading;
-        const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin;
+        const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin;
         const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         const noValueHeader = `NO ${key.toUpperCase()} VALUE`;
-        const evContents = heading ? heading : this.props?.type === 'number' ? '0' : noValueHeader;
-        const headingView = this.props.headingObject ? (
+        const evContents = heading ? heading : this._props?.type === 'number' ? '0' : noValueHeader;
+        const headingView = this._props.headingObject ? (
             <div
                 key={heading}
                 className="collectionStackingView-sectionHeader"
                 ref={this._headerRef}
                 style={{
-                    marginTop: this.props.yMargin,
-                    width: this.props.columnWidth / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1),
+                    marginTop: this._props.yMargin,
+                    width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
                 }}>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
                     Further, it does not have a color and cannot be deleted. */}
@@ -326,35 +337,35 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                     )} */}
                 </div>
                 <div
-                    className={'collectionStackingView-collapseBar' + (this.props.headingObject.collapsed === true ? ' active' : '')}
-                    style={{ display: this.props.headingObject.collapsed === true ? 'block' : undefined }}
+                    className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')}
+                    style={{ display: this._props.headingObject.collapsed === true ? 'block' : undefined }}
                     onClick={this.collapseSection}
                 />
             </div>
         ) : null;
-        const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `;
-        const type = this.props.Document.type;
+        const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `;
+        const type = this._props.Document.type;
         return (
             <>
-                {this.props.Document._columnsHideIfEmpty ? null : headingView}
+                {this._props.Document._columnsHideIfEmpty ? null : headingView}
                 {this.collapsed ? null : (
                     <div>
                         <div
                             key={`${heading}-stack`}
                             className={`collectionStackingView-masonrySingle`}
                             style={{
-                                padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`,
+                                padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
                                 margin: 'auto',
                                 width: 'max-content', //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
                                 height: 'max-content',
                                 position: 'relative',
-                                gridGap: this.props.gridGap,
+                                gridGap: this._props.gridGap,
                                 gridTemplateColumns: templatecols,
                                 gridAutoRows: '0px',
                             }}>
-                            {this.props.renderChildren(this.props.docList)}
+                            {this._props.renderChildren(this._props.docList)}
                         </div>
-                        {!this.props.chromeHidden && type !== DocumentType.PRES ? (
+                        {!this._props.chromeHidden && type !== DocumentType.PRES ? (
                             // TODO: this is the "new" button: see what you can work with here
                             // change cursor to pointer for this, and update dragging cursor
                             //TODO: there is a bug that occurs when adding a freeform document and trying to move it around
@@ -365,7 +376,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                                 key={`${heading}-add-document`}
                                 onKeyDown={e => e.stopPropagation()}
                                 className="collectionStackingView-addDocumentButton"
-                                style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}>
+                                style={{ width: 'calc(100% - 25px)', maxWidth: this._props.columnWidth / this._props.numGroupColumns - 25, marginBottom: 10 }}>
                                 <EditableView
                                     GetValue={returnEmptyString}
                                     SetValue={this.addNewTextDoc}
@@ -384,7 +395,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
 
     render() {
         TraceMobx();
-        const headings = this.props.headings();
+        const headings = this._props.headings();
         const heading = this._heading;
         const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
         return (
@@ -392,7 +403,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 className={'collectionStackingViewFieldColumn' + (SnappingManager.GetIsDragging() ? 'Dragging' : '')}
                 key={heading}
                 style={{
-                    width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`,
+                    width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`,
                     height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background,
                 }}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 40b2f9644..fd53e12da 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, override, runInAction, untracked } from 'mobx';
 import * as rp from 'request-promise';
 import CursorField from '../../../fields/CursorField';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
@@ -35,6 +35,17 @@ export function CollectionSubView<X>(moreProps?: X) {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
+        @override _props: X & SubCollectionViewProps;
+
+        constructor(props: any) {
+            super(props);
+            this._props = props;
+            makeObservable(this);
+        }
+        componentDidUpdate() {
+            // untracked(() => (this._props = this.props));
+        }
+
         @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
         @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
         protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
@@ -55,29 +66,29 @@ export function CollectionSubView<X>(moreProps?: X) {
             this.gestureDisposer?.();
         }
 
-        @computed get dataDoc() {
-            return this.props.TemplateDataDocument instanceof Doc && this.props.Document.isTemplateForField
-                ? Doc.GetProto(this.props.TemplateDataDocument)
-                : this.props.Document.resolvedDataDoc
-                ? this.props.Document
-                : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
+        get dataDoc() {
+            return this._props.TemplateDataDocument instanceof Doc && this._props.Document.isTemplateForField
+                ? Doc.GetProto(this._props.TemplateDataDocument)
+                : this._props.Document.resolvedDataDoc
+                  ? this._props.Document
+                  : Doc.GetProto(this._props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template
         }
 
         // this returns whether either the collection is selected, or the template that it is part of is selected
-        rootSelected = () => this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.());
+        rootSelected = () => this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
 
-        // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
+        // The data field for rendering this collection will be on the this._props.Document unless we're rendering a template in which case we try to use props.TemplateDataDocument.
         // When a document has a TemplateDataDoc but it's not a template, then it contains its own rendering data, but needs to pass the TemplateDataDoc through
         // to its children which may be templates.
         // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
         @computed get dataField() {
-            return this.dataDoc[this.props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout.  hopefully nothing depending on the previous code.
+            return this.dataDoc[this._props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout.  hopefully nothing depending on the previous code.
         }
 
         @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
-            const { Document, TemplateDataDocument } = this.props;
+            const { Document, TemplateDataDocument } = this._props;
             const validPairs = this.childDocs
-                .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
+                .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc))
                 .filter(pair => {
                     // filter out any documents that have a proto that we don't have permissions to
                     return !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));
@@ -87,14 +98,14 @@ export function CollectionSubView<X>(moreProps?: X) {
         @computed get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
-        collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._childFilters);
-        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._childFiltersByRanges, listSpec('string'), []);
+        collectionFilters = () => this._focusFilters ?? StrListCast(this._props.Document._childFilters);
+        collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this._props.Document._childFiltersByRanges, listSpec('string'), []);
         // child filters apply to the descendants of the documents in this collection
-        childDocFilters = () => [...(this.props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
+        childDocFilters = () => [...(this._props.childFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()];
         // unrecursive filters apply to the documents in the collection, but no their children.  See Utils.noRecursionHack
-        unrecursiveDocFilters = () => [...(this.props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
-        childDocRangeFilters = () => [...(this.props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
-        searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs);
+        unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])];
+        childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
+        searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this._props.Document._searchFilterDocs);
         @computed.struct get childDocs() {
             TraceMobx();
             let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -108,27 +119,27 @@ export function CollectionSubView<X>(moreProps?: X) {
                 // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc.
                 // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection.
                 // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image.
-                const templateRoot = this.props.TemplateDataDocument;
-                rawdocs = templateRoot && !this.props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
+                const templateRoot = this._props.TemplateDataDocument;
+                rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
             }
 
-            const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
+            const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
 
             const childDocFilters = this.childDocFilters();
             const childFiltersByRanges = this.childDocRangeFilters();
             const searchDocs = this.searchFilterDocs();
-            if (this.props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
+            if (this._props.Document.dontRegisterView || (!childDocFilters.length && !this.unrecursiveDocFilters().length && !childFiltersByRanges.length && !searchDocs.length)) {
                 return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
             }
 
             const docsforFilter: Doc[] = [];
             childDocs.forEach(d => {
                 // dragging facets
-                const dragged = this.props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
+                const dragged = this._props.childFilters?.().some(f => f.includes(Utils.noDragDocsFilter));
                 if (dragged && SnappingManager.GetCanEmbed() && DragManager.docsBeingDragged.includes(d)) return false;
-                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.props.Document).length > 0;
+                let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this._props.Document).length > 0;
                 if (notFiltered) {
-                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.props.Document).length > 0;
+                    notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this._props.Document).length > 0;
                     const fieldKey = Doc.LayoutFieldKey(d);
                     const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name);
                     const data = d[annos ? fieldKey + '_annotations' : fieldKey];
@@ -161,7 +172,7 @@ export function CollectionSubView<X>(moreProps?: X) {
         @action
         protected async setCursorPosition(position: [number, number]) {
             let ind;
-            const doc = this.props.Document;
+            const doc = this._props.Document;
             const id = Doc.UserDoc()[Id];
             const email = Doc.CurrentUserEmail;
             const pos = { x: position[0], y: position[1] };
@@ -197,23 +208,23 @@ export function CollectionSubView<X>(moreProps?: X) {
                 const dropAction = this.layoutDoc.dropAction as dropActionType;
                 // if the dropEvent's dragAction is, say 'embed', but we're just dragging within a collection, we may not actually want to make an embedding.
                 // so we check if our collection has a dropAction set on it and if so, we use that instead.
-                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this.props.Document && this.childDocs.includes(d))) {
+                if (dropAction && !de.complete.docDragData.draggedDocuments.some(d => d.embedContainer === this._props.Document && this.childDocs.includes(d))) {
                     de.complete.docDragData.dropAction = dropAction;
                 }
                 e.stopPropagation();
             }
         }
 
-        addDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.addDocument?.(doc, annotationKey) || false;
-        removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this.props.removeDocument?.(doc, annotationKey) || false;
-        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this.props.moveDocument?.(doc, targetCollection, addDocument);
-        @action
+        addDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.addDocument?.(doc, annotationKey) || false;
+        removeDocument = (doc: Doc | Doc[], annotationKey?: string) => this._props.removeDocument?.(doc, annotationKey) || false;
+        moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string) => this._props.moveDocument?.(doc, targetCollection, addDocument);
+
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
             if (docDragData) {
                 let added = undefined;
                 const dropAction = docDragData.dropAction || docDragData.userDropAction;
-                const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
+                const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
                 const someMoved = !dropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
                 if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop));
                 if ((!dropAction || dropAction === 'inSame' || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) {
@@ -236,13 +247,13 @@ export function CollectionSubView<X>(moreProps?: X) {
                     added = this.addDocument(docDragData.droppedDocuments);
                     !added && alert('You cannot perform this move');
                 }
-                added === false && !this.props.isAnnotationOverlay && e.preventDefault();
+                added === false && !this._props.isAnnotationOverlay && e.preventDefault();
                 added === true && e.stopPropagation();
                 return added ? true : false;
             } else if (de.complete.annoDragData) {
                 const dropCreator = de.complete.annoDragData.dropDocCreator;
                 de.complete.annoDragData.dropDocCreator = () => {
-                    const dropped = dropCreator(this.props.isAnnotationOverlay ? this.Document : undefined);
+                    const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined);
                     this.addDocument(dropped);
                     return dropped;
                 };
@@ -252,7 +263,6 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         @undoBatch
-        @action
         protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) {
             if (e.ctrlKey) {
                 e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
@@ -463,15 +473,15 @@ export function CollectionSubView<X>(moreProps?: X) {
             }
             if (generatedDocuments.length) {
                 // Creating a dash document
-                const isFreeformView = this.props.Document._type_collection === CollectionViewType.Freeform;
+                const isFreeformView = this._props.Document._type_collection === CollectionViewType.Freeform;
                 const set = !isFreeformView
                     ? generatedDocuments
                     : generatedDocuments.length > 1
-                    ? generatedDocuments.map(d => {
-                          DocUtils.iconify(d);
-                          return d;
-                      })
-                    : [];
+                      ? generatedDocuments.map(d => {
+                            DocUtils.iconify(d);
+                            return d;
+                        })
+                      : [];
                 if (completed) completed(set);
                 else {
                     if (isFreeformView && generatedDocuments.length > 1) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 5c165fe70..938af002f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
+import { copyProps, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -23,7 +23,7 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import './CollectionTreeView.scss';
 import { TreeView } from './TreeView';
 import * as React from 'react';
@@ -59,27 +59,35 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes
     private observer: any; // observer for monitoring tree view items.
 
-    @computed get doc() {
-        return this.props.Document;
+    _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
+    @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
+    constructor(props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
     }
-    @computed get dataDoc() {
-        return this.props.TemplateDataDocument || this.doc;
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
+    get dataDoc() {
+        return this._props.TemplateDataDocument || this.Document;
     }
     @computed get treeViewtruncateTitleWidth() {
-        return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth());
+        return NumCast(this.Document.treeView_TruncateTitleWidth, this.panelWidth());
     }
     @computed get treeChildren() {
         TraceMobx();
-        return this.props.childDocuments || this.childDocs;
+        return this._props.childDocuments || this.childDocs;
     }
     @computed get outlineMode() {
-        return this.doc.treeView_Type === TreeViewType.outline;
+        return this.Document.treeView_Type === TreeViewType.outline;
     }
     @computed get fileSysMode() {
-        return this.doc.treeView_Type === TreeViewType.fileSystem;
+        return this.Document.treeView_Type === TreeViewType.fileSystem;
     }
     @computed get dashboardMode() {
-        return this.doc === Doc.MyDashboards;
+        return this.Document === Doc.MyDashboards;
     }
 
     @observable _titleHeight = 0; // height of the title bar
@@ -88,8 +96,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
 
     // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent
     @observable _isAnyChildContentActive = false;
-    whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
-    isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this.props.isContentActive() ? true : false);
+    whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+    isContentActive = (outsideReaction?: boolean) => (this._isAnyChildContentActive ? true : this._props.isContentActive() ? true : false);
 
     componentWillUnmount() {
         this._isDisposing = true;
@@ -99,7 +107,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     componentDidMount() {
-        //this.props.setContentView?.(this);
+        //this._props.setContentView?.(this);
         this._disposers.autoheight = reaction(
             () => this.layoutDoc.layout_autoHeight,
             auto => auto && this.computeHeight(),
@@ -112,7 +120,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', ''));
             const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()) + 6;
             this.layoutDoc._layout_autoHeightMargins = bodyHeight;
-            !this.props.dontRegisterView && this.props.setHeight?.(bodyHeight + titleHeight);
+            !this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight);
         }
     };
     unobserveHeight = (ref: any) => {
@@ -135,7 +143,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     };
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer?.();
-        if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this));
+        if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
     };
 
     protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -143,7 +151,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const dragData = de.complete.docDragData;
         if (dragData) {
             const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
-            const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.doc && this.childDocs.includes(d));
+            const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
             if (isAlreadyInTree() !== sameTree) {
                 console.log('WHAAAT');
             }
@@ -152,22 +160,22 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
-    screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight);
 
     @action
     remove = (doc: Doc | Doc[]): boolean => {
         const docs = doc instanceof Doc ? [doc] : doc;
-        const targetDataDoc = this.doc[DocData];
-        const value = DocListCast(targetDataDoc[this.props.fieldKey]);
+        const targetDataDoc = this.Document[DocData];
+        const value = DocListCast(targetDataDoc[this._props.fieldKey]);
         const result = value.filter(v => !docs.includes(v));
         if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
         if (result.length !== value.length && doc instanceof Doc) {
-            const ind = DocListCast(targetDataDoc[this.props.fieldKey]).indexOf(doc);
-            const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1];
-            this.props.removeDocument?.(doc);
+            const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+            const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
+            this._props.removeDocument?.(doc);
             if (ind > 0 && prev) {
                 FormattedTextBox.SetSelectOnLoad(prev);
-                DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
+                DocumentManager.Instance.getDocumentView(prev, this._props.DocumentView?.())?.select(false);
             }
             return true;
         }
@@ -178,24 +186,28 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
         const doAddDoc = (doc: Doc | Doc[]) =>
             (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
-                const res = flg && Doc.AddDocToList(this.doc[DocData], this.props.fieldKey, doc, relativeTo, before);
-                res && Doc.SetContainer(doc, this.props.Document);
+                const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before);
+                res && Doc.SetContainer(doc, this.Document);
                 return res;
             }, true);
-        if (this.doc.resolvedDataDoc instanceof Promise) return false;
-        return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs);
+        if (this.Document.resolvedDataDoc instanceof Promise) return false;
+        return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs);
     };
     onContextMenu = (e: React.MouseEvent): void => {
         // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
         if (!Doc.noviceMode) {
             const layoutItems: ContextMenuProps[] = [];
-            layoutItems.push({ description: 'Make tree state ' + (this.doc.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeView_OpenIsTransient = !this.doc.treeView_OpenIsTransient), icon: 'paint-brush' });
-            layoutItems.push({ description: (this.doc.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeView_HideHeaderFields = !this.doc.treeView_HideHeaderFields), icon: 'paint-brush' });
-            layoutItems.push({ description: (this.doc.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeView_HideTitle = !this.doc.treeView_HideTitle), icon: 'paint-brush' });
+            layoutItems.push({
+                description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
+                event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
+                icon: 'paint-brush',
+            });
+            layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
+            layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
             ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
             const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
             const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
-            onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
+            onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
             !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
         }
     };
@@ -213,7 +225,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 height={'auto'}
                 GetValue={() => StrCast(this.dataDoc.title)}
                 SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => {
-                    if (enter && this.props.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
+                    if (enter && this.Document.treeView_Type === TreeViewType.outline) this.makeTextCollection(this.treeChildren);
                     this.dataDoc.title = value;
                     return true;
                 })}
@@ -231,9 +243,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     get documentTitle() {
         return (
             <FormattedTextBox
-                {...this.props}
+                {...this._props}
                 fieldKey="text"
-                renderDepth={this.props.renderDepth + 1}
+                renderDepth={this._props.renderDepth + 1}
                 isContentActive={this.isContentActive}
                 isDocumentActive={this.isContentActive}
                 forceAutoHeight={true} // needed to make the title resize even if the rest of the tree view is not layout_autoHeight
@@ -253,52 +265,52 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         );
     }
     childContextMenuItems = () => {
-        const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
-        const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
-        const icons = StrListCast(this.doc.childContextMenuIcons);
-        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+        const icons = StrListCast(this.Document.childContextMenuIcons);
+        return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
-    headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeView_HideHeaderFields);
+    headerFields = () => this._props.treeViewHideHeaderFields || BoolCast(this.Document.treeView_HideHeaderFields);
     @observable _renderCount = 1;
     @computed get treeViewElements() {
         TraceMobx();
-        const dragAction = StrCast(this.doc.childDragAction) as dropActionType;
+        const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
         const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
-        const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false;
+        const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
         if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
         return TreeView.GetChildElements(
             this.treeChildren,
             this,
             this,
-            this.doc,
-            this.props.TemplateDataDocument,
+            this.Document,
+            this._props.TemplateDataDocument,
             undefined,
             undefined,
             addDoc,
             this.remove,
             moveDoc,
             dragAction,
-            this.props.addDocTab,
-            this.props.styleProvider,
+            this._props.addDocTab,
+            this._props.styleProvider,
             this.screenToLocalTransform,
             this.isContentActive,
             this.panelWidth,
-            this.props.renderDepth,
+            this._props.renderDepth,
             this.headerFields,
             [],
-            this.props.onCheckedClick,
+            this._props.onCheckedClick,
             this.onChildClick,
-            this.props.treeViewSkipFields,
+            this._props.treeViewSkipFields,
             true,
             this.whenChildContentsActiveChanged,
-            this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, 'boolean', null),
+            this._props.dontRegisterView || Cast(this.Document.childDontRegisterViews, 'boolean', null),
             this.observeHeight,
             this.unobserveHeight,
             this.childContextMenuItems(),
             //TODO: [AL] add these
-            this.props.AddToMap,
-            this.props.RemFromMap,
-            this.props.hierarchyIndex,
+            this._props.AddToMap,
+            this._props.RemFromMap,
+            this._props.hierarchyIndex,
             this._renderCount
         );
     }
@@ -306,8 +318,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.dataDoc === null ? null : (
             <div
                 className="collectionTreeView-titleBar"
-                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))}
-                key={this.doc[Id]}
+                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))}
+                key={this.Document[Id]}
                 style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
             </div>
@@ -327,26 +339,26 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 <DocumentView
                     Document={menuDoc}
                     TemplateDataDocument={menuDoc}
-                    isContentActive={this.props.isContentActive}
+                    isContentActive={this._props.isContentActive}
                     isDocumentActive={returnTrue}
-                    addDocument={this.props.addDocument}
-                    moveDocument={this.props.moveDocument}
-                    removeDocument={this.props.removeDocument}
-                    addDocTab={this.props.addDocTab}
-                    pinToPres={this.props.pinToPres}
+                    addDocument={this._props.addDocument}
+                    moveDocument={this._props.moveDocument}
+                    removeDocument={this._props.removeDocument}
+                    addDocTab={this._props.addDocTab}
+                    pinToPres={this._props.pinToPres}
                     rootSelected={this.rootSelected}
                     ScreenToLocalTransform={Transform.Identity}
                     PanelWidth={this.return35}
                     PanelHeight={this.return35}
-                    renderDepth={this.props.renderDepth + 1}
+                    renderDepth={this._props.renderDepth + 1}
                     focus={emptyFunction}
-                    styleProvider={this.props.styleProvider}
+                    styleProvider={this._props.styleProvider}
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    childFilters={this.props.childFilters}
-                    childFiltersByRanges={this.props.childFiltersByRanges}
-                    searchFilterDocs={this.props.searchFilterDocs}
+                    childFilters={this._props.childFilters}
+                    childFiltersByRanges={this._props.childFiltersByRanges}
+                    searchFilterDocs={this._props.searchFilterDocs}
                 />
             </div>
         );
@@ -363,30 +375,30 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     @computed get nativeDimScaling() {
         const nw = this.nativeWidth;
         const nh = this.nativeHeight;
-        const hscale = nh ? this.props.PanelHeight() / nh : 1;
-        const wscale = nw ? this.props.PanelWidth() / nw : 1;
+        const hscale = nh ? this._props.PanelHeight() / nh : 1;
+        const wscale = nw ? this._props.PanelWidth() / nw : 1;
         return wscale < hscale ? wscale : hscale;
     }
-    marginX = () => NumCast(this.doc._xMargin);
-    marginTop = () => NumCast(this.doc._yMargin);
-    marginBot = () => NumCast(this.doc._yMargin);
+    marginX = () => NumCast(this.Document._xMargin);
+    marginTop = () => NumCast(this.Document._yMargin);
+    marginBot = () => NumCast(this.Document._yMargin);
     documentTitleWidth = () => Math.min(NumCast(this.layoutDoc?._width), this.panelWidth());
     documentTitleHeight = () => NumCast(this.layoutDoc?._height) - NumCast(this.layoutDoc.layout_autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
-    onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
-    panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
+    onChildClick = () => this._props.onChildClick?.() || ScriptCast(this.Document.onChildClick);
+    panelWidth = () => Math.max(0, this._props.PanelWidth() - 2 * this.marginX() * (this._props.NativeDimScaling?.() || 1));
 
-    addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this.props.fieldKey}_annotations`) || false;
-    remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this.props.fieldKey}_annotations`) || false;
+    addAnnotationDocument = (doc: Doc | Doc[]) => this.addDocument(doc, `${this._props.fieldKey}_annotations`) || false;
+    remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false;
     moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) =>
-        this.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}_annotations`) || false;
+        this.moveDocument(doc, targetCollection, addDocument, `${this._props.fieldKey}_annotations`) || false;
 
     @observable _headerHeight = 0;
     @computed get content() {
-        const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
-        const color = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.Color);
-        const pointerEvents = () => (this.props.isContentActive() === false ? 'none' : undefined);
-        const titleBar = this.props.treeViewHideTitle || this.doc.treeView_HideTitle ? null : this.titleBar;
+        const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
+        const color = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.Color);
+        const pointerEvents = () => (this._props.isContentActive() === false ? 'none' : undefined);
+        const titleBar = this._props.treeViewHideTitle || this.Document.treeView_HideTitle ? null : this.titleBar;
         return (
             <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
                 {!this.buttonMenu && !this.noviceExplainer ? null : (
@@ -398,7 +410,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 <div
                     className="collectionTreeView-contents"
                     key="tree"
-                    ref={r => !this.doc.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
+                    ref={r => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r)}
                     style={{
                         ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
                         color: color(),
@@ -423,7 +435,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                                 minHeight: '100%',
                             }}
                             onWheel={e => e.stopPropagation()}
-                            onClick={e => (!this.layoutDoc.forceActive ? this.props.select(false) : SelectionManager.DeselectAll())}
+                            onClick={e => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())}
                             onDrop={this.onTreeDrop}>
                             <ul className={`no-indent${this.outlineMode ? '-outline' : ''}`}>{this.treeViewElements}</ul>
                         </div>
@@ -435,27 +447,27 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     render() {
         TraceMobx();
 
-        const scale = this.props.NativeDimScaling?.() || 1;
+        const scale = this._props.NativeDimScaling?.() || 1;
         return (
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
-                {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeView_HasOverlay ? (
+                {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? (
                     <CollectionFreeFormView
-                        {...this.props}
+                        {...this._props}
                         setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
-                        pointerEvents={this.props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
+                        pointerEvents={this._props.isContentActive() && SnappingManager.GetIsDragging() ? returnAll : returnNone}
                         isAnnotationOverlay={true}
                         isAnnotationOverlayScrollable={true}
-                        childDocumentsActive={this.props.isDocumentActive}
-                        fieldKey={this.props.fieldKey + '_annotations'}
+                        childDocumentsActive={this._props.isDocumentActive}
+                        fieldKey={this._props.fieldKey + '_annotations'}
                         dropAction="move"
                         select={emptyFunction}
                         addDocument={this.addAnnotationDocument}
                         removeDocument={this.remAnnotationDocument}
                         moveDocument={this.moveAnnotationDocument}
                         bringToFront={emptyFunction}
-                        renderDepth={this.props.renderDepth + 1}>
+                        renderDepth={this._props.renderDepth + 1}>
                         {this.content}
                     </CollectionFreeFormView>
                 ) : (
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2e4c5af6b..e84c12e02 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -6,7 +6,7 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnEmptyString } from '../../../Utils';
+import { copyProps, returnEmptyString } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { dropActionType } from '../../util/DragManager';
@@ -76,12 +76,19 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         this._safeMode = safeMode;
     }
     private reactionDisposer: IReactionDisposer | undefined;
-    @observable _isContentActive: boolean | undefined;
+    @observable _isContentActive: boolean | undefined = undefined;
 
+    _prevProps: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>;
+    @override _props: React.PropsWithChildren<ViewBoxAnnotatableProps & CollectionViewProps>;
     constructor(props: any) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         runInAction(() => (this._annotationKeySuffix = returnEmptyString));
     }
+    componentDidUpdate() {
+        copyProps(this);
+    }
 
     componentDidMount() {
         // we use a reaction/observable instead of a computed value to reduce invalidations.
@@ -89,7 +96,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         // will cause downstream invalidations even if the computed value doesn't change.  By making
         // this a reaction, downstream invalidations only occur when the reaction value actually changes.
         this.reactionDisposer = reaction(
-            () => (this.isAnyChildContentActive() ? true : this.props.isContentActive()),
+            () => (this.isAnyChildContentActive() ? true : this._props.isContentActive()),
             active => (this._isContentActive = active),
             { fireImmediately: true }
         );
@@ -112,7 +119,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         return viewField as any as CollectionViewType;
     }
 
-    screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()));
+    screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
     // prettier-ignore
     private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
         TraceMobx();
@@ -172,7 +179,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                 this.setupViewTypes('Appearance...', vtype => {
                     const newRendition = Doc.MakeEmbedding(this.Document);
                     newRendition._type_collection = vtype;
-                    this.props.addDocTab(newRendition, OpenWhere.addRight);
+                    this._props.addDocTab(newRendition, OpenWhere.addRight);
                     return newRendition;
                 });
 
@@ -180,10 +187,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             const optionItems = options && 'subitems' in options ? options.subitems : [];
             !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.Document.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.Document.forceActive = !this.Document.forceActive), icon: 'project-diagram' }) : null;
             if (this.Document.childLayout instanceof Doc) {
-                optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
+                optionItems.push({ description: 'View Child Layout', event: () => this._props.addDocTab(this.Document.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
             if (this.Document.childClickedOpenTemplateView instanceof Doc) {
-                optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
+                optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
             !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox), icon: 'project-diagram' });
 
@@ -203,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                         event: (obj: any) => {
                             const embedding = Doc.MakeEmbedding(this.Document);
                             DocUtils.makeCustomViewClicked(embedding, undefined, func.key);
-                            this.props.addDocTab(embedding, OpenWhere.addRight);
+                            this._props.addDocTab(embedding, OpenWhere.addRight);
                         },
                     })
                 );
@@ -226,34 +233,40 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         }
     };
 
-    bodyPanelWidth = () => this.props.PanelWidth();
+    bodyPanelWidth = () => this._props.PanelWidth();
 
-    childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
+    childLayoutTemplate = () => this._props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
     isContentActive = (outsideReaction?: boolean) => this._isContentActive;
 
+    pointerEvents = () => {
+        const viewPath = this._props.DocumentView?.()?._props.docViewPath();
+        return (
+            this.layoutDoc._lockedPosition && //
+            viewPath?.lastElement()?.Document?._type_collection === CollectionViewType.Freeform
+        );
+    };
+
     render() {
         TraceMobx();
+        const pointerEvents = this.pointerEvents() ? 'none' : undefined;
         const props: SubCollectionViewProps = {
-            ...this.props,
+            ...this._props,
             addDocument: this.addDocument,
             moveDocument: this.moveDocument,
             removeDocument: this.removeDocument,
             isContentActive: this.isContentActive,
             isAnyChildContentActive: this.isAnyChildContentActive,
             PanelWidth: this.bodyPanelWidth,
-            PanelHeight: this.props.PanelHeight,
+            PanelHeight: this._props.PanelHeight,
             ScreenToLocalTransform: this.screenToLocalTransform,
             childLayoutTemplate: this.childLayoutTemplate,
             whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
-            childLayoutString: StrCast(this.Document.childLayoutString, this.props.childLayoutString),
-            childHideResizeHandles: this.props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
-            childHideDecorationTitle: this.props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
+            childLayoutString: StrCast(this.Document.childLayoutString, this._props.childLayoutString),
+            childHideResizeHandles: this._props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
+            childHideDecorationTitle: this._props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
         };
         return (
-            <div
-                className="collectionView"
-                onContextMenu={this.onContextMenu}
-                style={{ pointerEvents: this.props.DocumentView?.()?.props.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform && this.layoutDoc._lockedPosition ? 'none' : undefined }}>
+            <div className="collectionView" onContextMenu={this.onContextMenu} style={{ pointerEvents }}>
                 {this.renderSubView(this.collectionViewType, props)}
             </div>
         );
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4a1d702b8..dc7ee206c 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Popup, Type } from 'browndash-components';
 import { clamp } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc, Opt } from '../../../fields/Doc';
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
 import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -48,6 +48,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     static _allTabs = new ObservableSet<TabDocView>();
     _mainCont: HTMLDivElement | null = null;
     _tabReaction: IReactionDisposer | undefined;
+
+    _prevProps: React.PropsWithChildren<TabDocViewProps>;
+    @observable _props: React.PropsWithChildren<TabDocViewProps>;
+    constructor(props: React.PropsWithChildren<TabDocViewProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
     @observable _activated: boolean = false;
     @observable _panelWidth = 0;
     @observable _panelHeight = 0;
@@ -57,21 +66,21 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     @computed get _isUserActivated() {
         return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive;
     }
-    @computed get _isContentActive() {
+    get _isContentActive() {
         return this._isUserActivated || this._hovering;
     }
-    @observable _document: Doc | undefined;
-    @observable _view: DocumentView | undefined;
+    @observable _document: Doc | undefined = undefined;
+    @observable _view: DocumentView | undefined = undefined;
 
     @computed get layoutDoc() {
         return this._document && Doc.Layout(this._document);
     }
 
     get stack() {
-        return (this.props as any).glContainer.parent.parent;
+        return this._props.glContainer.parent.parent;
     }
     get tab() {
-        return (this.props as any).glContainer.tab;
+        return this._props.glContainer.tab;
     }
     get view() {
         return this._view;
@@ -162,12 +171,12 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                                           this._isUserActivated
                                               ? 0
                                               : this._hovering
-                                              ? 0.25
-                                              : degree === Doc.DocBrushStatus.selfBrushed
-                                              ? 0.5
-                                              : degree === Doc.DocBrushStatus.protoBrushed //
-                                              ? 0.7
-                                              : 0.9
+                                                ? 0.25
+                                                : degree === Doc.DocBrushStatus.selfBrushed
+                                                  ? 0.5
+                                                  : degree === Doc.DocBrushStatus.protoBrushed //
+                                                    ? 0.7
+                                                    : 0.9
                                       )
                                       .rgb()
                                       .toString()
@@ -316,7 +325,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
     }
 
-    @action
     componentDidMount() {
         new _global.ResizeObserver(
             action((entries: any) => {
@@ -325,25 +333,25 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     this._panelHeight = entry.contentRect.height;
                 }
             })
-        ).observe(this.props.glContainer._element[0]);
-        this.props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
-        this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
+        ).observe(this._props.glContainer._element[0]);
+        this._props.glContainer.layoutManager.on('activeContentItemChanged', this.onActiveContentItemChanged);
+        this._props.glContainer.tab?.isActive && this.onActiveContentItemChanged(undefined);
         // this._tabReaction = reaction(() => ({ selected: this.active(), title: this.tab?.titleElement[0] }),
         //     ({ selected, title }) => title && (title.style.backgroundColor = selected ? "white" : ""),
         //     { fireImmediately: true });
-        TabDocView._allTabs.add(this);
+        runInAction(() => TabDocView._allTabs.add(this));
     }
     componentDidUpdate() {
         this._view && DocumentManager.Instance.AddView(this._view);
+        copyProps(this);
     }
 
-    @action
     componentWillUnmount() {
         this._tabReaction?.();
         this._view && DocumentManager.Instance.RemoveView(this._view);
-        TabDocView._allTabs.delete(this);
+        runInAction(() => TabDocView._allTabs.delete(this));
 
-        this.props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
+        this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
     }
 
     // Flag indicating that when a tab is activated, it should not select it's document.
@@ -420,7 +428,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
-        return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+        return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
     PanelHeight = () => this._panelHeight;
@@ -441,8 +449,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         this._lastView = this._view;
                     })}
                     renderDepth={0}
-                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
-                    hideTitle={this.props.keyValue}
+                    LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+                    hideTitle={this._props.keyValue}
                     Document={this._document}
                     TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
                     onBrowseClick={DocumentView.exploreMode}
@@ -489,7 +497,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         }
                         this._lastTab = this.tab;
                         (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document);
-                        DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
+                        DocServer.GetRefField(this._props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document)));
                         new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref);
                     }
                 }}>
@@ -547,8 +555,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
             }
         }
     };
+
+    @observable _props: React.PropsWithChildren<TabMinimapViewProps>;
+    constructor(props: React.PropsWithChildren<TabMinimapViewProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
     @computed get renderBounds() {
-        const compView = this.props.tabView()?.ComponentView as CollectionFreeFormView;
+        const compView = this._props.tabView()?.ComponentView as CollectionFreeFormView;
         const bounds = compView?.freeformData?.(true)?.bounds;
         if (!bounds) return undefined;
         const xbounds = bounds.r - bounds.x;
@@ -556,10 +571,10 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
         const dim = Math.max(xbounds, ybounds);
         return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
     }
-    childLayoutTemplate = () => Cast(this.props.document.childLayoutTemplate, Doc, null);
-    returnMiniSize = () => NumCast(this.props.document._miniMapSize, 150);
+    childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
+    returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
     miniDown = (e: React.PointerEvent) => {
-        const doc = this.props.document;
+        const doc = this._props.document;
         const miniSize = this.returnMiniSize();
         doc &&
             setupMoveUpEvents(
@@ -578,15 +593,15 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
     popup = () => {
         if (!this.renderBounds) return <></>;
         const renderBounds = this.renderBounds;
-        const miniWidth = () => (this.props.PanelWidth() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniHeight = () => (this.props.PanelHeight() / NumCast(this.props.document._freeform_scale, 1) / renderBounds.dim) * 100;
-        const miniLeft = () => 50 + ((NumCast(this.props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
-        const miniTop = () => 50 + ((NumCast(this.props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
+        const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100;
+        const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2;
+        const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2;
         const miniSize = this.returnMiniSize();
         return (
-            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this.props.background() }}>
+            <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}>
                 <CollectionFreeFormView
-                    Document={this.props.document}
+                    Document={this._props.document}
                     docViewPath={returnEmptyDoclist}
                     childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this.
                     noOverlay={true} // don't render overlay Docs since they won't scale
@@ -596,7 +611,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     select={emptyFunction}
                     isSelected={returnFalse}
                     dontRegisterView={true}
-                    fieldKey={Doc.LayoutFieldKey(this.props.document)}
+                    fieldKey={Doc.LayoutFieldKey(this._props.document)}
                     bringToFront={emptyFunction}
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
@@ -608,7 +623,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
                     styleProvider={TabMinimapView.miniStyleProvider}
-                    addDocTab={this.props.addDocTab}
+                    addDocTab={this._props.addDocTab}
                     pinToPres={TabDocView.PinDoc}
                     childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist}
                     childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
@@ -622,7 +637,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
         );
     };
     render() {
-        return this.props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this.props.document)) || this.props.document?._type_collection !== CollectionViewType.Freeform ? null : (
+        return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
             <div className="miniMap-hidden">
                 <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
             </div>
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index ac79e4fef..27ff7166d 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IconButton, Size } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -12,7 +12,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -44,7 +44,7 @@ export interface TreeViewProps {
     observeHeight: (ref: any) => void;
     unobserveHeight: (ref: any) => void;
     prevSibling?: Doc;
-    document: Doc;
+    Document: Doc;
     dataDoc?: Doc;
     treeViewParent: Doc;
     renderDepth: number;
@@ -100,57 +100,61 @@ export class TreeView extends React.Component<TreeViewProps> {
     private _treedropDisposer?: DragManager.DragDropDisposer;
 
     get treeViewOpenIsTransient() {
-        return this.props.treeView.doc.treeView_OpenIsTransient || Doc.IsDataProto(this.doc);
+        return this.treeView.Document.treeView_OpenIsTransient || Doc.IsDataProto(this.Document);
     }
     set treeViewOpen(c: boolean) {
         if (this.treeViewOpenIsTransient) this._transientOpenState = c;
         else {
-            this.doc.treeView_Open = c;
+            this.Document.treeView_Open = c;
             this._transientOpenState = false;
         }
     }
     @observable _transientOpenState = false; // override of the treeView_Open field allowing the display state to be independent of the document's state
     @observable _editTitle: boolean = false;
-    @observable _dref: DocumentView | undefined | null;
+    @observable _dref: DocumentView | undefined | null = undefined;
     get displayName() {
-        return 'TreeView(' + this.props.document.title + ')';
+        return 'TreeView(' + this.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
     get defaultExpandedView() {
-        return this.doc._type_collection === CollectionViewType.Docking
+        return this.Document._type_collection === CollectionViewType.Docking
             ? this.fieldKey
-            : this.props.treeView.dashboardMode
+            : this.treeView.dashboardMode
               ? this.fieldKey
-              : this.props.treeView.fileSysMode
-                ? this.doc.isFolder
+              : this.treeView.fileSysMode
+                ? this.Document.isFolder
                     ? this.fieldKey
                     : 'data' // file system folders display their contents (data).  used to be they displayed their embeddings but now its a tree structure and not a flat list
-                : this.props.treeView.outlineMode || this.childDocs
+                : this.treeView.outlineMode || this.childDocs
                   ? this.fieldKey
                   : Doc.noviceMode
                     ? 'layout'
-                    : StrCast(this.props.treeView.doc.treeView_ExpandedView, 'fields');
+                    : StrCast(this.treeView.Document.treeView_ExpandedView, 'fields');
     }
 
-    @computed get doc() {
-        return this.props.document;
+    @computed get treeView() {
+        return this._props.treeView;
+    }
+
+    @computed get Document() {
+        return this._props.Document;
     }
     @computed get treeViewOpen() {
-        return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
+        return (!this.treeViewOpenIsTransient && Doc.GetT(this.Document, 'treeView_Open', 'boolean', true)) || this._transientOpenState;
     }
     @computed get treeViewExpandedView() {
-        return this.validExpandViewTypes.includes(StrCast(this.doc.treeView_ExpandedView)) ? StrCast(this.doc.treeView_ExpandedView) : this.defaultExpandedView;
+        return this.validExpandViewTypes.includes(StrCast(this.Document.treeView_ExpandedView)) ? StrCast(this.Document.treeView_ExpandedView) : this.defaultExpandedView;
     }
     @computed get MAX_EMBED_HEIGHT() {
-        return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
+        return NumCast(this._props.treeViewParent.maxEmbedHeight, 200);
     }
     @computed get dataDoc() {
-        return this.doc[DocData];
+        return this.Document[DocData];
     }
     @computed get layoutDoc() {
-        return Doc.Layout(this.doc);
+        return Doc.Layout(this.Document);
     }
     @computed get fieldKey() {
-        return StrCast(this.doc._treeView_FieldKey, Doc.LayoutFieldKey(this.doc));
+        return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document));
     }
     @computed get childDocs() {
         return this.childDocList(this.fieldKey);
@@ -169,30 +173,30 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     childDocList(field: string) {
-        const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
-        return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field])));
+        const layout = Cast(Doc.LayoutField(this.Document), Doc, null);
+        return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field])));
     }
     moving: boolean = false;
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-        if (this.doc !== target && addDoc !== returnFalse) {
-            const canAdd1 = (this.props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this.props.parentTreeView?.doc.data)) instanceof ComputedField);
+        if (this.Document !== target && addDoc !== returnFalse) {
+            const canAdd1 = (this._props.parentTreeView as any).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField);
 
             // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse
-            if (canAdd1 && this.props.removeDoc?.(doc) === true) {
-                this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = true);
+            if (canAdd1 && this._props.removeDoc?.(doc) === true) {
+                this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = true);
                 const res = addDoc(doc);
-                this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.moving = false);
+                this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.moving = false);
                 return res;
             }
         }
         return false;
     };
-    @undoBatch @action remove = (doc: Doc | Doc[], key: string) => {
-        this.props.treeView.props.select(false);
+    @undoBatch remove = (doc: Doc | Doc[], key: string) => {
+        this.treeView._props.select(false);
         const ind = DocListCast(this.dataDoc[key]).indexOf(doc instanceof Doc ? doc : doc.lastElement());
 
         const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true);
-        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.props.treeView.props.DocumentView?.())?.select(false);
+        res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView._props.DocumentView?.())?.select(false);
         return res;
     };
 
@@ -212,17 +216,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     @action
     openLevel = (docView: DocumentView) => {
-        if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
+        if (this.Document.isFolder || Doc.IsSystem(this.Document)) {
             this.treeViewOpen = !this.treeViewOpen;
         } else {
             // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns,  (2) has no context field ... otherwise make a new embedding
-            const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
-            this.props.addDocTab(bestEmbedding, OpenWhere.lightbox);
+            const bestEmbedding = docView.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.Document) ? docView.Document : Doc.BestEmbedding(docView.Document);
+            this._props.addDocTab(bestEmbedding, OpenWhere.lightbox);
         }
     };
 
     @undoBatch
-    @action
     recurToggle = (childList: Doc[]) => {
         if (childList.length > 0) {
             childList.forEach(child => {
@@ -233,7 +236,6 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     @undoBatch
-    @action
     getRunningChildren = (childList: Doc[]) => {
         if (childList.length === 0) {
             return [];
@@ -253,23 +255,27 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     static GetRunningChildren = new Map<Doc, any>();
     static ToggleChildrenRun = new Map<Doc, () => void>();
-    constructor(props: any) {
+    _prevProps: TreeViewProps;
+    @observable _props: TreeViewProps;
+    constructor(props: TreeViewProps) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         if (!TreeView._openLevelScript) {
             TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' });
             TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' });
         }
-        this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!;
-        this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
+        this._openScript = Doc.IsSystem(this.Document) ? undefined : () => TreeView._openLevelScript!;
+        this._editTitleScript = Doc.IsSystem(this.Document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!;
 
         // set for child processing highligting
         this.dataDoc.hasChildren = this.childDocs.length > 0;
         // this.dataDoc.children = this.childDocs;
-        TreeView.ToggleChildrenRun.set(this.doc, () => {
+        TreeView.ToggleChildrenRun.set(this.Document, () => {
             this.recurToggle(this.childDocs);
         });
 
-        TreeView.GetRunningChildren.set(this.doc, () => {
+        TreeView.GetRunningChildren.set(this.Document, () => {
             return this.getRunningChildren(this.childDocs);
         });
     }
@@ -277,31 +283,32 @@ export class TreeView extends React.Component<TreeViewProps> {
     _treeEle: any;
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this._treedropDisposer?.();
-        ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc);
-        if (this._treeEle) this.props.unobserveHeight(this._treeEle);
-        this.props.observeHeight((this._treeEle = ele));
+        ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.Document);
+        if (this._treeEle) this._props.unobserveHeight(this._treeEle);
+        this._props.observeHeight((this._treeEle = ele));
     };
 
     componentWillUnmount() {
         this._renderTimer && clearTimeout(this._renderTimer);
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        this._treeEle && this.props.unobserveHeight(this._treeEle);
+        this._treeEle && this._props.unobserveHeight(this._treeEle);
         document.removeEventListener('pointermove', this.onDragMove, true);
         document.removeEventListener('pointermove', this.onDragUp, true);
         // TODO: [AL] add these
-        this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex);
+        this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex);
     }
 
     componentDidUpdate() {
+        copyProps(this);
         this._disposers.opening = reaction(
             () => this.treeViewOpen,
             open => !open && (this._renderCount = 20)
         );
-        this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+        this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
     }
 
     componentDidMount() {
-        this.props.hierarchyIndex !== undefined && this.props.AddToMap?.(this.doc, this.props.hierarchyIndex);
+        this._props.hierarchyIndex !== undefined && this._props.AddToMap?.(this.Document, this._props.hierarchyIndex);
     }
 
     onDragUp = (e: PointerEvent) => {
@@ -309,8 +316,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         document.removeEventListener('pointermove', this.onDragMove, true);
     };
     onPointerEnter = (e: React.PointerEvent): void => {
-        this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
-        if (e.buttons === 1 && SnappingManager.GetIsDragging() && this.props.isContentActive()) {
+        this._props.isContentActive(true) && Doc.BrushDoc(this.dataDoc);
+        if (e.buttons === 1 && SnappingManager.GetIsDragging() && this._props.isContentActive()) {
             this._header.current!.className = 'treeView-header';
             document.removeEventListener('pointermove', this.onDragMove, true);
             document.removeEventListener('pointerup', this.onDragUp, true);
@@ -333,7 +340,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const before = pt[1] < rect.top + rect.height / 2;
         const inside = pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length);
         this._header.current!.className = 'treeView-header';
-        if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.Document) {
+        if (!this.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.treeView.Document) {
             if (inside) this._header.current!.className += ' treeView-header-inside';
             else if (before) this._header.current!.className += ' treeView-header-above';
             else if (!before) this._header.current!.className += ' treeView-header-below';
@@ -371,18 +378,18 @@ export class TreeView extends React.Component<TreeViewProps> {
     makeTextCollection = () => {
         const bullet = TreeView.makeTextBullet();
         TreeView._editTitleOnLoad = { id: bullet[Id], parent: this };
-        return this.props.addDocument(bullet);
+        return this._props.addDocument(bullet);
     };
 
     makeFolder = () => {
         const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
-        TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
-        return this.props.addDocument(folder);
+        TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView };
+        return this._props.addDocument(folder);
     };
 
     preTreeDrop = (e: Event, de: DragManager.DropEvent) => {
         const dragData = de.complete.docDragData;
-        dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
+        dragData && (dragData.dropAction = this.treeView.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction);
     };
 
     @undoBatch
@@ -391,17 +398,17 @@ export class TreeView extends React.Component<TreeViewProps> {
         if (!this._header.current) return false;
         const rect = this._header.current.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
         if (de.complete.linkDragData) {
             const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor();
-            const destDoc = this.doc;
+            const destDoc = this.Document;
             DocUtils.MakeLink(sourceDoc, destDoc, { link_relationship: 'tree link' });
             e.stopPropagation();
             return true;
         }
         const docDragData = de.complete.docDragData;
         if (docDragData && pt[0] < rect.left + rect.width) {
-            if (docDragData.draggedDocuments[0] === this.doc) return true;
+            if (docDragData.draggedDocuments[0] === this.Document) return true;
             const added = this.dropDocuments(
                 docDragData.droppedDocuments, //
                 before,
@@ -409,7 +416,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 docDragData.dropAction,
                 docDragData.removeDocument,
                 docDragData.moveDocument,
-                docDragData.treeViewDoc === this.props.treeView.props.Document
+                docDragData.treeViewDoc === this.treeView.Document
             );
             e.stopPropagation();
             !added && e.preventDefault();
@@ -420,40 +427,40 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     dropping: boolean = false;
     dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, removeDocument: DragManager.RemoveFunction | undefined, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) {
-        const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, undefined, before);
+        const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
         const localAdd = (doc: Doc | Doc[]) => {
             const innerAdd = (doc: Doc) => {
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
-                dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+                dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
                 return added;
             };
             return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
         };
         const addDoc = inside ? localAdd : parentAddDoc;
         const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
-        const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
-        if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this.props.parentTreeView?.doc))) {
-            this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = true);
+        const canAdd = (!this.treeView.outlineMode && !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add')) || forceAdd;
+        if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
+            this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = true);
             const res = droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false);
-            this.props.parentTreeView instanceof TreeView && (this.props.parentTreeView.dropping = false);
+            this._props.parentTreeView instanceof TreeView && (this._props.parentTreeView.dropping = false);
             return res;
         }
         return false;
     }
 
     refTransform = (ref: HTMLDivElement | undefined | null) => {
-        if (!ref) return this.props.ScreenToLocalTransform();
+        if (!ref) return this._props.ScreenToLocalTransform();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
-        const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle());
-        const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+        const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
+        const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]);
     };
     docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
     getTransform = () => this.refTransform(this._tref.current);
-    embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
+    embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1);
     embeddedPanelHeight = () => {
-        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc;
         return Math.min(
             NumCast(layoutDoc._height),
             this.MAX_EMBED_HEIGHT,
@@ -463,7 +470,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 return layoutDoc._layout_fitWidth
                     ? !Doc.NativeHeight(layoutDoc)
                         ? NumCast(layoutDoc._height)
-                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.treeViewParent._height)))
+                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this._props.treeViewParent._height)))
                     : (this.embeddedPanelWidth() * NumCast(layoutDoc._height)) / NumCast(layoutDoc._width);
             })()
         );
@@ -471,16 +478,16 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get expandedField() {
         const ids: { [key: string]: string } = {};
         const rows: JSX.Element[] = [];
-        const doc = this.doc;
+        const doc = this.Document;
         doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
 
         for (const key of Object.keys(ids).slice().sort()) {
-            if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
+            if (this._props.skipFields?.includes(key) || key === 'title' || key === 'treeView_Open') continue;
             const contents = doc[key];
             let contentElement: (JSX.Element | null)[] | JSX.Element = [];
 
             let leftOffset = observable({ width: 0 });
-            const expandedWidth = () => this.props.panelWidth() - leftOffset.width;
+            const expandedWidth = () => this._props.panelWidth() - leftOffset.width;
             if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) {
                 const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key);
                 const moveDoc = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(doc, target, addDoc);
@@ -488,44 +495,44 @@ export class TreeView extends React.Component<TreeViewProps> {
                     const innerAdd = (doc: Doc) => {
                         const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                         const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                        dataIsComputed && Doc.SetContainer(doc, DocCast(this.doc.embedContainer));
+                        dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
                         return added;
                     };
                     return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
                 };
                 contentElement = TreeView.GetChildElements(
                     contents instanceof Doc ? [contents] : DocListCast(contents),
-                    this.props.treeView,
+                    this.treeView,
                     this,
                     doc,
                     undefined,
-                    this.props.treeViewParent,
-                    this.props.prevSibling,
+                    this._props.treeViewParent,
+                    this._props.prevSibling,
                     addDoc,
                     remDoc,
                     moveDoc,
-                    this.props.dragAction,
-                    this.props.addDocTab,
+                    this._props.dragAction,
+                    this._props.addDocTab,
                     this.titleStyleProvider,
-                    this.props.ScreenToLocalTransform,
-                    this.props.isContentActive,
+                    this._props.ScreenToLocalTransform,
+                    this._props.isContentActive,
                     expandedWidth,
-                    this.props.renderDepth,
-                    this.props.treeViewHideHeaderFields,
-                    [...this.props.renderedIds, doc[Id]],
-                    this.props.onCheckedClick,
-                    this.props.onChildClick,
-                    this.props.skipFields,
+                    this._props.renderDepth,
+                    this._props.treeViewHideHeaderFields,
+                    [...this._props.renderedIds, doc[Id]],
+                    this._props.onCheckedClick,
+                    this._props.onChildClick,
+                    this._props.skipFields,
                     false,
-                    this.props.whenChildContentsActiveChanged,
-                    this.props.dontRegisterView,
+                    this._props.whenChildContentsActiveChanged,
+                    this._props.dontRegisterView,
                     emptyFunction,
                     emptyFunction,
                     this.childContextMenuItems(),
                     // TODO: [AL] Add these
-                    this.props.AddToMap,
-                    this.props.RemFromMap,
-                    this.props.hierarchyIndex
+                    this._props.AddToMap,
+                    this._props.RemFromMap,
+                    this._props.hierarchyIndex
                 );
             } else {
                 contentElement = (
@@ -573,9 +580,9 @@ export class TreeView extends React.Component<TreeViewProps> {
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
-        const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
+        const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; icon: JSX.Element | string } }) ?? {};
         if (['links', 'annotations', 'embeddings', this.fieldKey].includes(expandKey)) {
-            const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
+            const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
             const sortKeys = Object.keys(sortings);
             const curSortIndex = Math.max(
                 0,
@@ -587,7 +594,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => {
                 // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't),
                 // then the modification would be done here
-                const ordering = StrCast(this.doc.treeView_SortCriterion);
+                const ordering = StrCast(this.Document.treeView_SortCriterion);
                 if (ordering === TreeSort.Zindex) {
                     const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering);
                     doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000;
@@ -596,7 +603,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 }
                 const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField;
                 const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
-                !dataIsComputed && added && Doc.SetContainer(doc, this.doc);
+                !dataIsComputed && added && Doc.SetContainer(doc, this.Document);
 
                 return added;
             };
@@ -614,12 +621,12 @@ export class TreeView extends React.Component<TreeViewProps> {
             }
             return (
                 <div>
-                    {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : (
+                    {!docs?.length || this._props.AddToMap /* hack to identify pres box trees */ ? null : (
                         <div className="treeView-sorting">
                             <IconButton
                                 color={sortings[sorting]?.color}
                                 size={Size.XSMALL}
-                                tooltip={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+                                tooltip={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
                                 icon={sortings[sorting]?.icon}
                                 onPointerDown={e => {
                                     downX = e.clientX;
@@ -627,8 +634,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                                     e.stopPropagation();
                                 }}
                                 onClick={undoable(e => {
-                                    if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
-                                        !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+                                    if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+                                        !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
                                         e.stopPropagation();
                                     }
                                 }, 'sort order')}
@@ -638,7 +645,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     <ul
                         style={{ cursor: 'inherit' }}
                         key={expandKey + 'more'}
-                        title={`Sorted by : ${this.doc.treeView_SortCriterion}. click to cycle`}
+                        title={`Sorted by : ${this.Document.treeView_SortCriterion}. click to cycle`}
                         className="" //this.doc.treeView_HideTitle ? 'no-indent' : ''}
                         onPointerDown={e => {
                             downX = e.clientX;
@@ -646,8 +653,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                             e.stopPropagation();
                         }}
                         onClick={undoable(e => {
-                            if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
-                                !this.props.treeView.outlineMode && (this.doc.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
+                            if (this._props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) {
+                                !this.treeView.outlineMode && (this.Document.treeView_SortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]);
                                 e.stopPropagation();
                             }
                         }, 'sort order')}>
@@ -655,37 +662,37 @@ export class TreeView extends React.Component<TreeViewProps> {
                             ? null
                             : TreeView.GetChildElements(
                                   docs,
-                                  this.props.treeView,
+                                  this.treeView,
                                   this,
                                   this.layoutDoc,
                                   this.dataDoc,
-                                  this.props.treeViewParent,
-                                  this.props.prevSibling,
+                                  this._props.treeViewParent,
+                                  this._props.prevSibling,
                                   addDoc,
                                   remDoc,
                                   moveDoc,
-                                  StrCast(this.doc.childDragAction, this.props.dragAction) as dropActionType,
-                                  this.props.addDocTab,
+                                  StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType,
+                                  this._props.addDocTab,
                                   this.titleStyleProvider,
-                                  this.props.ScreenToLocalTransform,
-                                  this.props.isContentActive,
-                                  this.props.panelWidth,
-                                  this.props.renderDepth,
-                                  this.props.treeViewHideHeaderFields,
-                                  [...this.props.renderedIds, this.doc[Id]],
-                                  this.props.onCheckedClick,
-                                  this.props.onChildClick,
-                                  this.props.skipFields,
+                                  this._props.ScreenToLocalTransform,
+                                  this._props.isContentActive,
+                                  this._props.panelWidth,
+                                  this._props.renderDepth,
+                                  this._props.treeViewHideHeaderFields,
+                                  [...this._props.renderedIds, this.Document[Id]],
+                                  this._props.onCheckedClick,
+                                  this._props.onChildClick,
+                                  this._props.skipFields,
                                   false,
-                                  this.props.whenChildContentsActiveChanged,
-                                  this.props.dontRegisterView,
+                                  this._props.whenChildContentsActiveChanged,
+                                  this._props.dontRegisterView,
                                   emptyFunction,
                                   emptyFunction,
                                   this.childContextMenuItems(),
                                   // TODO: [AL] add these
-                                  this.props.AddToMap,
-                                  this.props.RemFromMap,
-                                  this.props.hierarchyIndex,
+                                  this._props.AddToMap,
+                                  this._props.RemFromMap,
+                                  this._props.hierarchyIndex,
                                   this._renderCount
                               )}
                     </ul>
@@ -693,7 +700,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             );
         } else if (this.treeViewExpandedView === 'fields') {
             return (
-                <ul key={this.doc[Id] + this.doc.title} style={{ cursor: 'inherit' }}>
+                <ul key={this.Document[Id] + this.Document.title} style={{ cursor: 'inherit' }}>
                     <div>{this.expandedField}</div>
                 </ul>
             );
@@ -705,13 +712,13 @@ export class TreeView extends React.Component<TreeViewProps> {
                     e.preventDefault();
                     e.stopPropagation();
                 }}>
-                {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
+                {this.renderEmbeddedDocument(false, this.treeView._props.childDocumentsActive ?? returnFalse)}
             </ul>
         ); // "layout"
     }
 
     get onCheckedClick() {
-        return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick);
+        return this.Document.type === DocumentType.COL ? undefined : this._props.onCheckedClick?.() ?? ScriptCast(this.Document.onCheckedClick);
     }
 
     @action
@@ -719,10 +726,10 @@ export class TreeView extends React.Component<TreeViewProps> {
         if (this.onCheckedClick) {
             this.onCheckedClick?.script.run(
                 {
-                    this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
-                    heading: this.props.treeViewParent.title,
-                    checked: this.doc.treeView_Checked === 'check' ? 'x' : this.doc.treeView_Checked === 'x' ? 'remove' : 'check',
-                    containingTreeView: this.props.treeView.props.Document,
+                    this: this.Document.isTemplateForField && this._props.dataDoc ? this._props.dataDoc : this.Document,
+                    heading: this._props.treeViewParent.title,
+                    checked: this.Document.treeView_Checked === 'check' ? 'x' : this.Document.treeView_Checked === 'x' ? 'remove' : 'check',
+                    containingTreeView: this.treeView.Document,
                 },
                 console.log
             );
@@ -734,28 +741,28 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     @computed get renderBullet() {
         TraceMobx();
-        const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
+        const iconType = this.treeView._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : !this.childDocs.length ? ':empty' : '')) || 'question';
         const color = SettingsManager.userColor;
-        const checked = this.onCheckedClick ? this.doc.treeView_Checked ?? 'unchecked' : undefined;
+        const checked = this.onCheckedClick ? this.Document.treeView_Checked ?? 'unchecked' : undefined;
         return (
             <div
-                className={`bullet${this.props.treeView.outlineMode ? '-outline' : ''}`}
+                className={`bullet${this.treeView.outlineMode ? '-outline' : ''}`}
                 key="bullet"
-                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.props.document.type} content`}
+                title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : `view ${this.Document.type} content`}
                 onClick={this.bulletClick}
                 style={
-                    this.props.treeView.outlineMode
+                    this.treeView.outlineMode
                         ? {
-                              opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity),
+                              opacity: this.titleStyleProvider?.(this.Document, this.treeView._props, StyleProp.Opacity),
                           }
                         : {
-                              pointerEvents: this.props.isContentActive() ? 'all' : undefined,
+                              pointerEvents: this._props.isContentActive() ? 'all' : undefined,
                               opacity: checked === 'unchecked' || typeof iconType !== 'string' ? undefined : 0.4,
                               color: checked === 'unchecked' ? SettingsManager.userColor : 'inherit',
                           }
                 }>
-                {this.props.treeView.outlineMode ? (
-                    !(this.doc.text as RichTextField)?.Text ? null : (
+                {this.treeView.outlineMode ? (
+                    !(this.Document.text as RichTextField)?.Text ? null : (
                         <IconButton color={color} icon={<FontAwesomeIcon icon={[this.childDocs?.length && !this.treeViewOpen ? 'fas' : 'far', 'circle']} />} size={Size.XSMALL} />
                     )
                 ) : (
@@ -775,28 +782,28 @@ export class TreeView extends React.Component<TreeViewProps> {
     }
 
     @computed get validExpandViewTypes() {
-        const annos = () => (DocListCast(this.doc[this.fieldKey + '_annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : '');
-        const links = () => (LinkManager.Links(this.doc).length && !this.props.treeView.dashboardMode ? 'links' : '');
-        const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : '');
-        const embeddings = () => (this.props.treeView.dashboardMode ? '' : 'embeddings');
+        const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : '');
+        const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : '');
+        const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : '');
+        const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings');
         const fields = () => (Doc.noviceMode ? '' : 'fields');
-        const layout = Doc.noviceMode || this.doc._type_collection === CollectionViewType.Docking ? [] : ['layout'];
-        return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
+        const layout = Doc.noviceMode || this.Document._type_collection === CollectionViewType.Docking ? [] : ['layout'];
+        return [data(), ...layout, ...(this.treeView.fileSysMode ? [embeddings(), links(), annos()] : []), fields()].filter(m => m);
     }
     @action
     expandNextviewType = () => {
-        if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeView_ExpandedViewLock) {
+        if (this.treeViewOpen && !this.Document.isFolder && !this.treeView.outlineMode && !this.Document.treeView_ExpandedViewLock) {
             const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.treeViewExpandedView)) + 1) % modes.length];
-            this.doc.treeView_ExpandedView = next(this.validExpandViewTypes);
+            this.Document.treeView_ExpandedView = next(this.validExpandViewTypes);
         }
         this.treeViewOpen = true;
     };
 
     @observable headerEleWidth = 0;
     @computed get titleButtons() {
-        const customHeaderButtons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations);
+        const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations);
         const color = SettingsManager.userColor;
-        return this.props.treeViewHideHeaderFields() || this.doc.treeView_HideHeaderFields ? null : (
+        return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : (
             <>
                 {customHeaderButtons} {/* e.g.,. hide button is set by dashboardStyleProvider */}
                 <IconButton
@@ -808,7 +815,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                         e.stopPropagation();
                     }}
                 />
-                {Doc.noviceMode ? null : this.doc.treeView_ExpandedViewLock || Doc.IsSystem(this.doc) ? null : (
+                {Doc.noviceMode ? null : this.Document.treeView_ExpandedViewLock || Doc.IsSystem(this.Document) ? null : (
                     <span className="collectionTreeView-keyHeader" title="type of expanded data" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}>
                         {this.treeViewExpandedView}
                     </span>
@@ -829,50 +836,50 @@ export class TreeView extends React.Component<TreeViewProps> {
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' };
         const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' };
         return [
-            ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
-            ...(this.doc.isFolder
+            ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)),
+            ...(this.Document.isFolder
                 ? folderOp
-                : Doc.IsSystem(this.doc)
+                : Doc.IsSystem(this.Document)
                   ? []
-                  : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc)
+                  : this.treeView.fileSysMode && this.Document === Doc.GetProto(this.Document)
                     ? [openEmbedding, makeFolder]
-                    : this.doc._type_collection === CollectionViewType.Docking
+                    : this.Document._type_collection === CollectionViewType.Docking
                       ? []
-                      : this.props.treeView.Document === Doc.MyRecentlyClosed
+                      : this.treeView.Document === Doc.MyRecentlyClosed
                         ? [reopenDoc]
                         : [openEmbedding, focusDoc]),
         ];
     };
     childContextMenuItems = () => {
-        const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
-        const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
-        const icons = StrListCast(this.doc.childContextMenuIcons);
-        return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
+        const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []);
+        const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []);
+        const icons = StrListCast(this.Document.childContextMenuIcons);
+        return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
 
-    onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!);
+    onChildClick = () => this._props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!);
 
-    onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeView_ChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
+    onChildDoubleClick = () => ScriptCast(this.treeView.Document.treeView_ChildDoubleClick, !this.treeView.outlineMode ? this._openScript?.() : null);
 
-    refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {});
+    refocus = () => this.treeView._props.focus(this.treeView.Document, {});
     ignoreEvent = (e: any) => {
-        if (this.props.isContentActive(true)) {
+        if (this._props.isContentActive(true)) {
             e.stopPropagation();
             e.preventDefault();
         }
     };
     titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
-        if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+        if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
-        const treeView = this.props.treeView;
+        const treeView = this.treeView;
         // prettier-ignore
         switch (property.split(':')[0]) {
-            case StyleProp.Opacity:         return this.props.treeView.outlineMode ? undefined : 1;
+            case StyleProp.Opacity:         return this.treeView.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
-            case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
+            case StyleProp.Highlighting:    if (this.treeView.outlineMode) return undefined;
             case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
-                const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
+                const highlightIndex = this.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.GetBrushHighlightStatus(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 return treeView.outlineMode ? null : (
                     <div
@@ -882,32 +889,32 @@ export class TreeView extends React.Component<TreeViewProps> {
                             maxWidth: props?.PanelWidth() || undefined,
                             background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
                             outline: SnappingManager.GetIsDragging() ? undefined: `solid ${highlightColor} ${highlightIndex}px`,
-                            paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
-                            paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
-                            paddingTop: treeView.props.childYPadding,
-                            paddingBottom: treeView.props.childYPadding,
+                            paddingLeft: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingRight: NumCast(treeView.Document.childXPadding, NumCast(treeView._props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingTop: treeView._props.childYPadding,
+                            paddingBottom: treeView._props.childYPadding,
                         }}>
                         {StrCast(doc?.title)}
                     </div>
                 );
         }
-        return treeView.props.styleProvider?.(doc, props, property);
+        return treeView._props.styleProvider?.(doc, props, property);
     };
     embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
-        return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
+        return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
-        if (this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) {
+        if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) {
             switch (e.key) {
                 case 'Tab':
                     e.stopPropagation?.();
                     e.preventDefault?.();
                     setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
-                    UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab');
+                    UndoManager.RunInBatch(() => (e.shiftKey ? this._props.outdentDocument?.(true) : this._props.indentDocument?.(true)), 'tab');
                     return true;
                 case 'Backspace':
-                    if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) {
+                    if (!(this.Document.text as RichTextField)?.Text && this._props.removeDoc?.(this.Document)) {
                         e.stopPropagation?.();
                         e.preventDefault?.();
                         return true;
@@ -921,7 +928,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
         return false;
     };
-    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
+    titleWidth = () => Math.max(20, Math.min(this.treeView.truncateTitleWidth(), this._props.panelWidth())) / (this.treeView._props.NativeDimScaling?.() || 1) - this.headerEleWidth - treeBulletWidth();
 
     /**
      * Renders the EditableView title element for placement into the tree.
@@ -936,21 +943,21 @@ export class TreeView extends React.Component<TreeViewProps> {
                 display={'inline-block'}
                 editing={this._editTitle}
                 background={'#7089bb'}
-                contents={StrCast(this.doc.title)}
+                contents={StrCast(this.Document.title)}
                 height={12}
                 sizeToContent={true}
                 fontSize={12}
                 isEditingCallback={action(e => (this._editTitle = e))}
-                GetValue={() => StrCast(this.doc.title)}
+                GetValue={() => StrCast(this.Document.title)}
                 OnTab={undoBatch((shift?: boolean) => {
-                    if (!shift) this.props.indentDocument?.(true);
-                    else this.props.outdentDocument?.(true);
+                    if (!shift) this._props.indentDocument?.(true);
+                    else this._props.outdentDocument?.(true);
                 })}
-                OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))}
-                OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()}
+                OnEmpty={undoBatch(() => this.treeView.outlineMode && this._props.removeDoc?.(this.Document))}
+                OnFillDown={val => this.treeView.fileSysMode && this.makeFolder()}
                 SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => {
-                    Doc.SetInPlace(this.doc, 'title', value, false);
-                    this.props.treeView.outlineMode && enterKey && this.makeTextCollection();
+                    Doc.SetInPlace(this.Document, 'title', value, false);
+                    this.treeView.outlineMode && enterKey && this.makeTextCollection();
                 })}
             />
         ) : (
@@ -958,29 +965,29 @@ export class TreeView extends React.Component<TreeViewProps> {
                 key="title"
                 ref={action((r: any) => {
                     this._docRef = r ? r : undefined;
-                    if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) {
+                    if (this._docRef && TreeView._editTitleOnLoad?.id === this.Document[Id] && TreeView._editTitleOnLoad.parent === this._props.parentTreeView) {
                         this._docRef.select(false);
                         this.setEditTitle(this._docRef);
                         TreeView._editTitleOnLoad = undefined;
                     }
                 })}
-                Document={this.doc}
+                Document={this.Document}
                 layout_fitWidth={returnTrue}
                 scriptContext={this}
-                hideDecorationTitle={this.props.treeView.outlineMode}
-                hideResizeHandles={this.props.treeView.outlineMode}
+                hideDecorationTitle={this.treeView.outlineMode}
+                hideResizeHandles={this.treeView.outlineMode}
                 styleProvider={this.titleStyleProvider}
                 onClickScriptDisable="never" // tree docViews have a script to show fields, etc.
-                docViewPath={this.props.treeView.props.docViewPath}
-                treeViewDoc={this.props.treeView.props.Document}
+                docViewPath={this.treeView._props.docViewPath}
+                treeViewDoc={this.treeView.Document}
                 addDocument={undefined}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.treeView.props.pinToPres}
+                addDocTab={this._props.addDocTab}
+                pinToPres={this.treeView._props.pinToPres}
                 onClick={this.onChildClick}
                 onDoubleClick={this.onChildDoubleClick}
-                dragAction={this.props.dragAction}
+                dragAction={this._props.dragAction}
                 moveDocument={this.move}
-                removeDocument={this.props.removeDoc}
+                removeDocument={this._props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
                 NativeHeight={returnZero}
                 NativeWidth={returnZero}
@@ -988,16 +995,16 @@ export class TreeView extends React.Component<TreeViewProps> {
                 PanelHeight={return18}
                 contextMenuItems={this.contextMenuItems}
                 renderDepth={1}
-                isContentActive={emptyFunction} //this.props.isContentActive}
-                isDocumentActive={this.props.isContentActive}
+                isContentActive={emptyFunction} //this._props.isContentActive}
+                isDocumentActive={this._props.isContentActive}
                 focus={this.refocus}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 bringToFront={emptyFunction}
-                disableBrushing={this.props.treeView.props.disableBrushing}
-                hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
-                dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
-                xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
-                yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
+                disableBrushing={this.treeView._props.disableBrushing}
+                hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+                dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
+                xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+                yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
                 childFilters={returnEmptyFilter}
                 childFiltersByRanges={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
@@ -1006,16 +1013,16 @@ export class TreeView extends React.Component<TreeViewProps> {
         return (
             <>
                 <div
-                    className={`docContainer${Doc.IsSystem(this.props.document) || this.props.document.isFolder ? '-system' : ''}`}
+                    className={`docContainer${Doc.IsSystem(this.Document) || this.Document.isFolder ? '-system' : ''}`}
                     ref={this._tref}
                     title="click to edit title. Double Click or Drag to Open"
                     style={{
-                        backgroundColor: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? SettingsManager.userVariantColor : undefined,
-                        color: Doc.IsSystem(this.props.document) || this.props.document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
-                        fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? 'bold' : undefined,
-                        textDecoration: Doc.GetT(this.doc, 'title', 'string', true) ? 'underline' : undefined,
-                        outline: this.doc === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
-                        pointerEvents: !this.props.isContentActive() ? 'none' : undefined,
+                        backgroundColor: Doc.IsSystem(this.Document) || this.Document.isFolder ? SettingsManager.userVariantColor : undefined,
+                        color: Doc.IsSystem(this.Document) || this.Document.isFolder ? lightOrDark(SettingsManager.userVariantColor) : undefined,
+                        fontWeight: Doc.IsSearchMatch(this.Document) !== undefined ? 'bold' : undefined,
+                        textDecoration: Doc.GetT(this.Document, 'title', 'string', true) ? 'underline' : undefined,
+                        outline: this.Document === Doc.ActiveDashboard ? 'dashed 1px #06123232' : undefined,
+                        pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
                     }}>
                     {view}
                 </div>
@@ -1055,42 +1062,42 @@ export class TreeView extends React.Component<TreeViewProps> {
         return (
             <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
                 <DocumentView
-                    key={this.doc[Id]}
+                    key={this.Document[Id]}
                     ref={action((r: DocumentView | null) => (this._dref = r))}
-                    Document={this.doc}
+                    Document={this.Document}
                     layout_fitWidth={this.fitWidthFilter}
                     PanelWidth={this.embeddedPanelWidth}
                     PanelHeight={this.embeddedPanelHeight}
                     LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
-                    LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+                    LayoutTemplate={this.treeView._props.childLayoutTemplate}
                     isContentActive={isActive}
                     isDocumentActive={isActive}
                     styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
                     hideTitle={asText}
                     fitContentsToBox={returnTrue}
-                    hideDecorationTitle={this.props.treeView.outlineMode}
-                    hideResizeHandles={this.props.treeView.outlineMode}
+                    hideDecorationTitle={this.treeView.outlineMode}
+                    hideResizeHandles={this.treeView.outlineMode}
                     onClick={this.onChildClick}
                     focus={this.refocus}
                     onKey={this.onKeyDown}
-                    hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
-                    dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+                    hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)}
+                    dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)}
                     ScreenToLocalTransform={this.docTransform}
-                    renderDepth={this.props.renderDepth + 1}
-                    treeViewDoc={this.props.treeView?.props.Document}
-                    docViewPath={this.props.treeView.props.docViewPath}
+                    renderDepth={this._props.renderDepth + 1}
+                    treeViewDoc={this.treeView?.Document}
+                    docViewPath={this.treeView._props.docViewPath}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    addDocument={this.props.addDocument}
+                    addDocument={this._props.addDocument}
                     moveDocument={this.move}
-                    removeDocument={this.props.removeDoc}
-                    whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                    xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
-                    yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
-                    addDocTab={this.props.addDocTab}
-                    pinToPres={this.props.treeView.props.pinToPres}
-                    disableBrushing={this.props.treeView.props.disableBrushing}
+                    removeDocument={this._props.removeDoc}
+                    whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                    xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)}
+                    yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)}
+                    addDocTab={this._props.addDocTab}
+                    pinToPres={this.treeView._props.pinToPres}
+                    disableBrushing={this.treeView._props.disableBrushing}
                     bringToFront={returnFalse}
                     scriptContext={this}
                 />
@@ -1100,7 +1107,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     // renders the text version of a document as the header.  This is used in the file system mode and in other vanilla tree views.
     @computed get renderTitleAsHeader() {
-        return this.props.treeView.Document.treeView_HideUnrendered && this.doc.layout_unrendered && !this.doc.treeView_FieldKey ? (
+        return this.treeView.Document.treeView_HideUnrendered && this.Document.layout_unrendered && !this.Document.treeView_FieldKey ? (
             <div></div>
         ) : (
             <>
@@ -1115,16 +1122,16 @@ export class TreeView extends React.Component<TreeViewProps> {
         return (
             <>
                 {this.renderBullet}
-                {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
+                {this.renderEmbeddedDocument(asText, this._props.isContentActive)}
             </>
         );
     };
 
     @computed get renderBorder() {
-        const sorting = StrCast(this.doc.treeView_SortCriterion, TreeSort.WhenAdded);
-        const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
+        const sorting = StrCast(this.Document.treeView_SortCriterion, TreeSort.WhenAdded);
+        const sortings = (this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
         return (
-            <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
+            <div className={`treeView-border${this.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
                 {!this.treeViewOpen ? null : this.renderContent}
             </div>
         );
@@ -1134,28 +1141,28 @@ export class TreeView extends React.Component<TreeViewProps> {
         const pt = [de.clientX, de.clientY];
         const rect = this._header.current!.getBoundingClientRect();
         const before = pt[1] < rect.top + rect.height / 2;
-        const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
+        const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
 
-        const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
+        const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false));
     };
 
     render() {
         TraceMobx();
-        const hideTitle = this.doc.treeView_HideHeader || (this.doc.treeView_HideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
-        return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? (
-            '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
+        const hideTitle = this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode;
+        return this._props.renderedIds?.indexOf(this.Document[Id]) !== -1 ? (
+            '<' + this.Document.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
         ) : (
             <div
-                className={`treeView-container${this.props.isContentActive() ? '-active' : ''}`}
+                className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`}
                 ref={this.createTreeDropTarget}
                 onDrop={this.onTreeDrop}
-                //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
+                //onPointerDown={e => this._props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
                 // onKeyDown={this.onKeyDown}
             >
                 <li className="collection-child">
-                    {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
+                    {hideTitle && this.Document.type !== DocumentType.RTF && !this.Document.treeView_RenderAsBulletHeader // should test for prop 'treeView_RenderDocWithBulletAsHeader"
                         ? this.renderEmbeddedDocument(false, returnFalse)
-                        : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
+                        : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.Document.treeView_RenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
                 </li>
             </div>
         );
@@ -1231,7 +1238,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
 
         const docs = TreeView.sortDocs(childDocs, StrCast(treeView_Parent.treeView_SortCriterion, TreeSort.WhenAdded));
-        const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
+        const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView._props.NativeDimScaling?.() || 1);
         const treeView_Refs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
@@ -1243,7 +1250,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 }
 
                 const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
-                    if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
+                    if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return;
                     const fieldKey = Doc.LayoutFieldKey(newParent);
                     if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
                         remove(child);
@@ -1255,7 +1262,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 };
                 const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeView_Refs.get(docs[i - 1]));
-                const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined);
+                const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined);
                 const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false);
                 const childLayout = Doc.Layout(pair.layout);
                 const rowHeight = () => {
@@ -1266,7 +1273,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     <TreeView
                         key={child[Id]}
                         ref={r => treeView_Refs.set(child, r ? r : undefined)}
-                        document={pair.layout}
+                        Document={pair.layout}
                         dataDoc={pair.data}
                         treeViewParent={treeView_Parent}
                         prevSibling={docs[i]}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 3ba7aedef..dd7e12e41 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,6 +1,7 @@
-import { IReactionDisposer, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { copyProps } from '../../../../Utils';
 import './CollectionFreeFormView.scss';
 
 /**
@@ -49,8 +50,16 @@ export interface CollectionFreeFormInfoStateProps {
 export class CollectionFreeFormInfoState extends React.Component<CollectionFreeFormInfoStateProps> {
     _disposers: IReactionDisposer[] = [];
 
+    _prevProps: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
+    @observable _props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
+    constructor(props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
     get State() {
-        return this.props.infoState;
+        return this._props.infoState;
     }
     get Arcs() {
         return Object.keys(this.State).map(key => this.State[key]);
@@ -65,7 +74,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
                 res => {
                     if (res) {
                         const next = arc.act(res);
-                        this.props.next(next);
+                        this._props.next(next);
                     }
                 },
                 { fireImmediately: true }
@@ -76,6 +85,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
         this.initState();
     }
     componentDidUpdate() {
+        copyProps(this);
         this.clearState();
         this.initState();
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 1265dc2de..f0a052c1d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,4 +1,4 @@
-import { IReactionDisposer, computed, observable, reaction, action, runInAction } from 'mobx';
+import { IReactionDisposer, computed, observable, reaction, action, runInAction, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
@@ -10,11 +10,12 @@ import { NumCast } from '../../../../fields/Types';
 import { LinkManager } from '../../../util/LinkManager';
 import { InkTool } from '../../../../fields/InkField';
 import { LinkDocPreview } from '../../nodes/LinkDocPreview';
-import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { DocumentLinksButton, DocButtonState } from '../../nodes/DocumentLinksButton';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { CollectionFreeFormInfoState, infoState, StateMessage, infoArc, StateEntryFunc, InfoState } from './CollectionFreeFormInfoState';
 import { string32 } from 'pdfjs-dist/types/src/shared/util';
 import { any } from 'bluebird';
+import { copyProps } from '../../../../Utils';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
@@ -25,10 +26,23 @@ export interface CollectionFreeFormInfoUIProps {
 export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeFormInfoUIProps> {
     private _disposers: { [name: string]: IReactionDisposer } = {};
 
-    @observable currState!: infoState;
-    constructor(props: any) {
+    @observable _currState: infoState | undefined = undefined;
+    get currState() {
+        return this._currState!;
+    }
+    set currState(val) {
+        this._currState = val;
+    }
+    _prevProps: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
+    @observable _props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
+    constructor(props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>) {
         super(props);
-        this.setCurrState(this.setupStates());
+        this._props = this._prevProps = this.props;
+        makeObservable(this);
+        this.currState = this.setupStates();
+    }
+    componentDidUpdate() {
+        copyProps(this);
     }
 
     setCurrState = (state: infoState) => {
@@ -40,12 +54,12 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
 
     setupStates = () => {
         //  state entry functions
-        const setBackground = (col: string) => () => (this.props.Freeform.layoutDoc.backgroundColor = col);
+        const setBackground = (col: string) => () => (this._props.Freeform.layoutDoc.backgroundColor = col);
         //  arc transition trigger conditions
-        const firstDoc = () => this.props.Freeform.childDocs.lastElement();
-        const numDocs = () => this.props.Freeform.childDocs.length;
+        const firstDoc = () => this._props.Freeform.childDocs.lastElement();
+        const numDocs = () => this._props.Freeform.childDocs.length;
         const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length;
-        const linkMenuOpen = () => DocumentLinksButton.LinkEditorDocView;
+        const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView;
 
         // set of states.
         const start = InfoState('Click to create Object', {
@@ -78,7 +92,7 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
     /*
     componentDidMount(): void {
         this._disposers.reaction1 = reaction(
-            () => this.props.Freeform.childDocs.slice(),
+            () => this._props.Freeform.childDocs.slice(),
             docs => {
                 if (docs.length === 1) {
                     this.firstDoc = docs[0];
@@ -162,8 +176,8 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
     // this._disposers.reaction1();
 
     @observable message = 'Click anywhere and begin typing to create your first document!';
-    @observable firstDoc: Doc | undefined;
-    @observable secondDoc: Doc | undefined;
+    @observable firstDoc: Doc | undefined = undefined;
+    @observable secondDoc: Doc | undefined = undefined;
     */
     firstDocPos = { x: 0, y: 0 };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 4995925c8..5cbea4783 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -171,8 +171,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
 
     @action
     toggleProperties = () => {
-        if ((SettingsManager.propertiesWidth ?? 0) < 100) {
-            SettingsManager.propertiesWidth = 250;
+        if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+            SettingsManager.Instance.propertiesWidth = 250;
         }
     };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 03d302f39..154c914ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, toJS } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, copyProps, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -43,7 +43,7 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
 import { CreateImage } from '../../nodes/WebBoxRenderer';
 import { StyleProp } from '../../StyleProvider';
-import { CollectionSubView } from '../CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
@@ -69,9 +69,20 @@ export type collectionFreeformViewProps = {
 @observer
 export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
     public get displayName() {
-        return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')';
+        return 'CollectionFreeFormView(' + this._props.Document.title?.toString() + ')';
     } // this makes mobx trace() statements more descriptive
 
+    _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>;
+    @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionFreeformViewProps>>;
+    constructor(props: any) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable
     public static ShowPresPaths = false;
 
@@ -90,19 +101,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _brushtimer1: any;
 
     public get isAnnotationOverlay() {
-        return this.props.isAnnotationOverlay;
+        return this._props.isAnnotationOverlay;
     }
     public get scaleFieldKey() {
-        return (this.props.viewField ?? '') + '_freeform_scale';
+        return (this._props.viewField ?? '') + '_freeform_scale';
     }
     private get panXFieldKey() {
-        return (this.props.viewField ?? '') + '_freeform_panX';
+        return (this._props.viewField ?? '') + '_freeform_panX';
     }
     private get panYFieldKey() {
-        return (this.props.viewField ?? '') + '_freeform_panY';
+        return (this._props.viewField ?? '') + '_freeform_panY';
     }
     private get autoResetFieldKey() {
-        return (this.props.viewField ?? '') + '_freeform_autoReset';
+        return (this._props.viewField ?? '') + '_freeform_autoReset';
     }
 
     @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@@ -113,7 +124,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _deleteList: DocumentView[] = [];
     @observable _timelineRef = React.createRef<Timeline>();
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
-    @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
+    @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region
     @observable GroupChildDrag: boolean = false; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
 
     @computed get contentViews() {
@@ -130,11 +141,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             scale:
                 (!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid)
                     ? 1 //
-                    : Math.min(this.props.PanelHeight() / hgt, this.props.PanelWidth() / wid)) / (this.props.NativeDimScaling?.() || 1),
+                    : Math.min(this._props.PanelHeight() / hgt, this._props.PanelWidth() / wid)) / (this._props.NativeDimScaling?.() || 1),
         };
     }
     @computed get fitContentsToBox() {
-        return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
+        return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
     }
     @computed get contentBounds() {
         const cb = Cast(this.dataDoc.contentBounds, listSpec('number'));
@@ -147,30 +158,30 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
               );
     }
     @computed get nativeWidth() {
-        return this.props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
     }
     @computed get nativeHeight() {
-        return this.props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
     }
     @computed get cachedCenteringShiftX(): number {
         const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
-        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
-        const dv = this.props.DocumentView?.();
-        const fitWidth = this.props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
+        const dv = this._props.DocumentView?.();
+        const fitWidth = this._props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
         const scaling = !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         // if freeform has a native aspect, then the panel height needs to be adjusted to match it
-        const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
-        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        const aspect = dv?.nativeWidth && dv?.nativeHeight && !fitWidth ? dv.nativeHeight / dv.nativeWidth : this._props.PanelHeight() / this._props.PanelWidth();
+        return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : (aspect * this._props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get panZoomXf() {
         return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
     }
     @computed get screenToLocalXf() {
-        return this.props
+        return this._props
             .ScreenToLocalTransform()
-            .scale(this.props.isAnnotationOverlay ? 1 : 1)
+            .scale(this._props.isAnnotationOverlay ? 1 : 1)
             .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
             .transform(this.panZoomXf);
     }
@@ -219,9 +230,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _keyframeEditing = false;
     @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set);
     getKeyFrameEditing = () => this._keyframeEditing;
-    onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
-    onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
-    onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
+    onBrowseClickHandler = () => this._props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick);
+    onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
+    onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
     fitContentOnce = () => {
         const vals = this.fitToContentVals;
@@ -236,27 +247,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () =>
-        this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+        this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
     ScreenToLocalXf = () => this.screenToLocalXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
-    isAnyChildContentActive = () => this.props.isAnyChildContentActive();
+    isAnyChildContentActive = () => this._props.isAnyChildContentActive();
     addLiveTextBox = (newDoc: Doc) => {
         FormattedTextBox.SetSelectOnLoad(newDoc); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
         this.addDocument(newDoc);
     };
     selectDocuments = (docs: Doc[]) => {
         SelectionManager.DeselectAll();
-        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
+        docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
     };
     addDocument = (newBox: Doc | Doc[]) => {
         let retVal = false;
         if (newBox instanceof Doc) {
-            if ((retVal = this.props.addDocument?.(newBox) || false)) {
+            if ((retVal = this._props.addDocument?.(newBox) || false)) {
                 this.bringToFront(newBox);
                 this.updateCluster(newBox);
             }
         } else {
-            retVal = this.props.addDocument?.(newBox) || false;
+            retVal = this._props.addDocument?.(newBox) || false;
             // bcz: deal with clusters
         }
         if (retVal) {
@@ -270,7 +281,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
                 }
             }
-            if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+            if (this.Document._currentFrame !== undefined && !this._props.isAnnotationOverlay) {
                 CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
             }
         }
@@ -286,7 +297,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     groupFocus = (anchor: Doc, options: DocFocusOptions) => {
         options.docTransform = new Transform(-NumCast(this.layoutDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.layoutDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
-        const res = this.props.focus(this.Document, options);
+        const res = this._props.focus(this.Document, options);
         options.docTransform = undefined;
         return res;
     };
@@ -324,11 +335,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             findDoc(dv => res(dv));
         });
 
-    @action
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
         if (!super.onInternalDrop(e, de)) return false;
         const refDoc = docDragData.droppedDocuments[0];
-        const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+        const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
         const z = NumCast(refDoc.z);
         const x = (z ? xpo : xp) - docDragData.offset[0];
         const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -343,7 +353,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
-            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this.props.ScreenToLocalTransform().Rotate);
+            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -405,7 +415,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @undoBatch
     internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
-        if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
+        if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
             const source =
@@ -426,7 +436,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                           _xPadding: 0,
                           onClick: FollowLinkScript(),
                       });
-            added = this.props.addDocument?.(source) ? true : false;
+            added = this._props.addDocument?.(source) ? true : false;
             de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
 
             e.stopPropagation();
@@ -463,7 +473,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return this.childLayoutPairs
             .map(pair => pair.layout)
             .reduce((cluster, cd) => {
-                const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
+                const grouping = this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
                 if (grouping !== -1) {
                     const layoutDoc = Doc.Layout(cd);
                     const cx = NumCast(cd.x) - this._clusterDistance / 2;
@@ -480,11 +490,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (cluster !== -1) {
             const ptsParent = e;
             if (ptsParent) {
-                const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
-                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
+                const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+                const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this._props.DocumentView?.())!);
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
-                de.moveDocument = this.props.moveDocument;
+                de.moveDocument = this._props.moveDocument;
                 de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 DragManager.StartDocumentDrag(
                     clusterDocs.map(v => v.ContentDiv!),
@@ -502,7 +512,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     updateClusters(_freeform_useClusters: boolean) {
-        this.props.Document._freeform_useClusters = _freeform_useClusters;
+        this._props.Document._freeform_useClusters = _freeform_useClusters;
         this._clusterSets.length = 0;
         this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
     }
@@ -510,7 +520,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateClusterDocs(docs: Doc[]) {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this.props.Document._freeform_useClusters) {
+        if (this._props.Document._freeform_useClusters) {
             const docFirst = docs[0];
             docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
             const preferredInd = NumCast(docFirst.layout_cluster);
@@ -553,7 +563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     updateCluster = (doc: Doc) => {
         const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
-        if (this.props.Document._freeform_useClusters) {
+        if (this._props.Document._freeform_useClusters) {
             this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
             const preferredInd = NumCast(doc.layout_cluster);
             doc.layout_cluster = -1;
@@ -583,7 +593,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
-        let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
+        let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
             switch (property) {
                 case StyleProp.BackgroundColor:
@@ -612,7 +622,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     trySelectCluster = (addToSel: boolean) => {
         if (this._hitCluster !== -1) {
             !addToSel && SelectionManager.DeselectAll();
-            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+            const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this._props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
             this.selectDocuments(eles);
             return true;
         }
@@ -625,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this._downY = this._lastY = e.pageY;
         this._downTime = Date.now();
         const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
-        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) {
+        if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive(true)) {
             if (!this.Document.isGroup) {
                 // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
                 // prettier-ignore
@@ -638,7 +648,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
                         break;
                     case InkTool.None:
-                        if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
+                        if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
                             this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
                         }
@@ -661,7 +671,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
                 const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkWidth = ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale;
+                const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale;
                 const inkDoc = Docs.Create.InkDocument(
                     points,
                     { title: ge.gesture.toString(),
@@ -710,7 +720,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this._lightboxDoc) this._lightboxDoc = undefined;
         if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) {
             if (this.onBrowseClickHandler()) {
-                this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
+                this.onBrowseClickHandler().script.run({ documentView: this._props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY });
                 e.stopPropagation();
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
@@ -733,9 +743,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const ctrlKey = e.ctrlKey && !e.shiftKey;
         const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
-        this.props.DocumentView?.().clearViewTransition();
+        this._props.DocumentView?.().clearViewTransition();
         const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.props.ScreenToLocalTransform().Rotate);
+        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate);
         this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
@@ -788,7 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return true;
         }
         // pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
-        if (!this.props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) {
+        if (!this._props.isAnnotationOverlay || 1 - NumCast(this.layoutDoc._freeform_scale_min, 1) / this.zoomScaling()) {
             this.pan(e);
             e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
         }
@@ -804,7 +814,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
 
         return this.childDocs
-            .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
+            .map(doc => DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.()))
             .filter(inkView => inkView?.ComponentView instanceof InkingStroke)
             .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
             .filter(
@@ -901,7 +911,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs
             .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
             .forEach(doc => {
-                const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
+                const otherInk = DocumentManager.Instance.getDocumentView(doc, this._props.DocumentView?.())?.ComponentView as InkingStroke;
                 const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
                 const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
                 const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -933,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoom = (pointX: number, pointY: number, deltaY: number): void => {
-        if (this.Document.isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
+        if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
         const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
@@ -955,8 +965,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
-            this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
+            this._props.Document[this.scaleFieldKey] = Math.abs(safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, (this._props.originTopLeft ? undefined : NumCast(this._props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
         }
     };
 
@@ -964,10 +974,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onPointerWheel = (e: React.WheelEvent): void => {
         if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
-        if (this.layoutDoc._Transform || this.props.Document.treeView_OutlineMode === TreeViewType.outline) return;
+        if (this.layoutDoc._Transform || this._props.Document.treeView_OutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
-        const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling + 1e-4;
+        const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4;
         switch (
             !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
                 Doc.UserDoc().freeformScrollMode : // no modifiers, do assigned mode
@@ -975,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         freeformScrollMode.Zoom : freeformScrollMode.Pan // prettier-ignore
         ) {
             case freeformScrollMode.Pan:
-                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) {
+                if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
                     const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
                     const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
                     this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
@@ -983,8 +993,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }
             default:
             case freeformScrollMode.Zoom:
-                if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
-                    this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                if ((e.ctrlKey || !scrollable) && this._props.isContentActive(true)) {
+                    this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this._props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     e.preventDefault();
                 }
                 break;
@@ -1013,19 +1023,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
                     }),
                     {
-                        xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
-                        yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        xrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        yrange: { min: this._props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
                     }
                 );
-                const scaling = this.zoomScaling() * (this.props.NativeDimScaling?.() || 1);
-                const panelWidMax = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
-                const panelWidMin = (this.props.PanelWidth() / scaling) * (this.props.originTopLeft ? 0 : 1);
-                const panelHgtMax = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
-                const panelHgtMin = (this.props.PanelHeight() / scaling) * (this.props.originTopLeft ? 0 : 1);
-                if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
-                else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
-                if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
-                else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
+                const scaling = this.zoomScaling() * (this._props.NativeDimScaling?.() || 1);
+                const panelWidMax = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelWidMin = (this._props.PanelWidth() / scaling) * (this._props.originTopLeft ? 0 : 1);
+                const panelHgtMax = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelHgtMin = (this._props.PanelHeight() / scaling) * (this._props.originTopLeft ? 0 : 1);
+                if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this._props.originTopLeft ? 0 : panelWidMax / 2);
+                else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this._props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+                if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this._props.originTopLeft ? 0 : panelHgtMax / 2);
+                else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this._props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
             }
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
@@ -1036,13 +1046,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
             const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
-            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
-            const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
-            const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale;
+            const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+            const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
                 scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
-                (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+                (!this._props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
             let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
             if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
                 const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
@@ -1061,11 +1071,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        const collectionDoc = this.props.docViewPath().lastElement().Document;
+        const collectionDoc = this._props.docViewPath().lastElement().Document;
         if (collectionDoc?._type_collection !== CollectionViewType.Freeform) {
             this.setPan(
-                NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
-                NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+                NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+                NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(),
                 nudgeTime,
                 true
             );
@@ -1132,20 +1142,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const newScale =
                 scale === 0
                     ? NumCast(this.layoutDoc[this.scaleFieldKey])
-                    : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+                    : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this._props.PanelWidth() / Math.abs(bounds.width), this._props.PanelHeight() / Math.abs(bounds.height)));
             return {
-                panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
-                panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
+                panX: this._props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
+                panY: this._props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
                 scale: newScale,
             };
         }
 
-        const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth();
-        const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
+        const panelWidth = this._props.isAnnotationOverlay ? this.nativeWidth : this._props.PanelWidth();
+        const panelHeight = this._props.isAnnotationOverlay ? this.nativeHeight : this._props.PanelHeight();
         const pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1);
         const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1);
-        const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
-        const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+        const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this._props.isAnnotationOverlay ? pw / 2 : 0);
+        const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this._props.isAnnotationOverlay ? ph / 2 : 0);
         const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
         const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
         const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1153,19 +1163,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             return {
                 panX: (bounds.left + bounds.right) / 2,
                 panY: (bounds.top + bounds.bot) / 2,
-                scale: Math.min(this.props.PanelHeight() / (bounds.bot - bounds.top), this.props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
+                scale: Math.min(this._props.PanelHeight() / (bounds.bot - bounds.top), this._props.PanelWidth() / (bounds.right - bounds.left)) / 1.1,
             };
         }
         return {
-            panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
-            panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+            panX: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+            panY: (this._props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
         };
     };
 
-    isContentActive = () => this.props.isContentActive();
+    isContentActive = () => this._props.isContentActive();
 
     @undoBatch
-    @action
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         const docView = fieldProps.DocumentView?.();
         if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
@@ -1186,21 +1195,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
     @computed get childPointerEvents() {
-        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+        const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
         const pointerevents = DocumentView.Interacting
             ? 'none'
-            : this.props.childPointerEvents?.() ??
-              (this.props.viewDefDivClick || //
-              (engine === computePassLayout.name && !this.props.isSelected()) ||
+            : this._props.childPointerEvents?.() ??
+              (this._props.viewDefDivClick || //
+              (engine === computePassLayout.name && !this._props.isSelected()) ||
               this.isContentActive() === false
                   ? 'none'
-                  : this.props.pointerEvents?.());
+                  : this._props.pointerEvents?.());
         return pointerevents;
     }
 
-    @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined;
+    @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined;
     childPointerEventsFunc = () => this._childPointerEvents;
-    childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
+    childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
     getChildDocView(entry: PoolData) {
         const childLayout = entry.pair.layout;
         const childData = entry.pair.data;
@@ -1212,50 +1221,50 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 TemplateDataDocument={childData}
                 dragStarting={this.dragStarting}
                 dragEnding={this.dragEnding}
-                isGroupActive={this.props.isGroupActive}
-                renderDepth={this.props.renderDepth + 1}
+                isGroupActive={this._props.isGroupActive}
+                renderDepth={this._props.renderDepth + 1}
                 hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)}
                 suppressSetHeight={this.layoutEngine ? true : false}
                 RenderCutoffProvider={this.renderCutoffProvider}
                 CollectionFreeFormView={this}
-                LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
-                LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
+                LayoutTemplate={childLayout.z ? undefined : this._props.childLayoutTemplate}
+                LayoutTemplateString={childLayout.z ? undefined : this._props.childLayoutString}
                 rootSelected={childData ? this.rootSelected : returnFalse}
-                waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
+                waitForDoubleClickToClick={this._props.waitForDoubleClickToClick}
                 onClick={this.onChildClickHandler}
                 onKey={this.onKeyDown}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 onBrowseClick={this.onBrowseClickHandler}
-                ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf}
+                ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+                isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
                 isContentActive={this.childContentsActive}
-                focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
+                focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus}
                 addDocTab={this.addDocTab}
-                addDocument={this.props.addDocument}
-                removeDocument={this.props.removeDocument}
-                moveDocument={this.props.moveDocument}
-                pinToPres={this.props.pinToPres}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                docViewPath={this.props.docViewPath}
+                addDocument={this._props.addDocument}
+                removeDocument={this._props.removeDocument}
+                moveDocument={this._props.moveDocument}
+                pinToPres={this._props.pinToPres}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                docViewPath={this._props.docViewPath}
                 styleProvider={this.clusterStyleProvider}
-                dragAction={(this.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
+                dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 bringToFront={this.bringToFront}
-                layout_showTitle={this.props.childlayout_showTitle}
-                dontRegisterView={this.props.dontRegisterView}
+                layout_showTitle={this._props.childlayout_showTitle}
+                dontRegisterView={this._props.dontRegisterView}
                 pointerEvents={this.childPointerEventsFunc}
             />
         );
     }
     addDocTab = action((doc: Doc, where: OpenWhere) => {
-        if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where);
+        if (this._props.isAnnotationOverlay) return this._props.addDocTab(doc, where);
         switch (where) {
             case OpenWhere.inParent:
-                return this.props.addDocument?.(doc) || false;
+                return this._props.addDocument?.(doc) || false;
             case OpenWhere.inParentFromScreen:
                 const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
                 return (
@@ -1267,7 +1276,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             return doc;
                         })
                     ) &&
-                        (!docContext || this.props.removeDocument?.(docContext))) ||
+                        (!docContext || this._props.removeDocument?.(docContext))) ||
                     false
                 );
             case undefined:
@@ -1281,9 +1290,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     return true;
                 }
         }
-        return this.props.addDocTab(doc, where);
+        return this._props.addDocTab(doc, where);
     });
-    @observable _lightboxDoc: Opt<Doc>;
+    @observable _lightboxDoc: Opt<Doc> = undefined;
 
     getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
         const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
@@ -1295,7 +1304,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
         const { x, y, autoDim, _width, _height, opacity, _rotation } =
             layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value)
-                ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+                ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() }
                 : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
         // prettier-ignore
         const rotation = Cast(_rotation,'number',
@@ -1307,9 +1316,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             z: Cast(z, 'number'),
             autoDim,
             rotation,
-            color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
-            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor),
-            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+            color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color),
+            backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor),
+            opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity),
             zIndex: Cast(zIndex, 'number'),
             width: _width,
             height: _height,
@@ -1321,7 +1330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
-        (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
+        (this._props.viewDefDivClick || ScriptCast(this._props.Document.onViewDefDivClick))?.script.run({ this: this._props.Document, payload });
         e.stopPropagation();
     };
 
@@ -1376,7 +1385,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         poolData: Map<string, PoolData>,
         engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[]
     ) {
-        return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps);
+        return engine(poolData, this._props.Document, this.childLayoutPairs, [this._props.PanelWidth(), this._props.PanelHeight()], this.viewDefsToJSX, this._props.engineProps);
     }
 
     doFreeformLayout(poolData: Map<string, PoolData>) {
@@ -1385,7 +1394,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @computed get layoutEngine() {
-        return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+        return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
     }
     @computed get doInternalLayoutComputation() {
         TraceMobx();
@@ -1423,10 +1432,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document);
 
         if (addAsAnnotation) {
-            if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
-                Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
+            if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
             } else {
-                this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
+                this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
             }
         }
         return anchor;
@@ -1435,7 +1444,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     infoUI = () => <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />;
 
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         super.componentDidMount?.();
         setTimeout(
             action(() => {
@@ -1473,15 +1482,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
                 this._disposers.pointerevents = reaction(
                     () => {
-                        const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+                        const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
                         return DocumentView.Interacting
                             ? 'none'
-                            : this.props.childPointerEvents?.() ??
-                                  (this.props.viewDefDivClick || //
-                                  (engine === computePassLayout.name && !this.props.isSelected()) ||
+                            : this._props.childPointerEvents?.() ??
+                                  (this._props.viewDefDivClick || //
+                                  (engine === computePassLayout.name && !this._props.isSelected()) ||
                                   this.isContentActive() === false
                                       ? 'none'
-                                      : this.props.pointerEvents?.());
+                                      : this._props.pointerEvents?.());
                     },
                     pointerevents => (this._childPointerEvents = pointerevents as any),
                     { fireImmediately: true }
@@ -1536,11 +1545,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     updateIcon = () =>
         CollectionFreeFormView.UpdateIcon(
             this.layoutDoc[Id] + '-icon' + new Date().getTime(),
-            this.props.docViewPath().lastElement().ContentDiv!,
+            this._props.docViewPath().lastElement().ContentDiv!,
             NumCast(this.layoutDoc._width),
             NumCast(this.layoutDoc._height),
-            this.props.PanelWidth(),
-            this.props.PanelHeight(),
+            this._props.PanelWidth(),
+            this._props.PanelHeight(),
             0,
             1,
             false,
@@ -1600,7 +1609,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
-        this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
+        this._props.addDocTab(childDocs as any as Doc, OpenWhere.inParentFromScreen);
     };
 
     @undoBatch
@@ -1643,28 +1652,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.isAnnotationOverlay || !ContextMenu.Instance) return;
+        if (this._props.isAnnotationOverlay || !ContextMenu.Instance) return;
 
         const appearance = ContextMenu.Instance.findByDescription('Appearance...');
         const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
-        !this.props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
-        !this.props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
+        !this._props.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
+        !this._props.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
         !Doc.noviceMode &&
             appearanceItems.push({
                 description: 'Toggle auto arrange',
                 event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
                 icon: 'compress-arrows-alt',
             });
-        if (this.props.setContentView === emptyFunction) {
+        if (this._props.setContentView === emptyFunction) {
             !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
             return;
         }
         !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
-        appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
+        appearanceItems.push({ description: `Pin View`, event: () => this._props.pinToPres(this.Document, { pinViewport: MarqueeView.CurViewBounds(this.dataDoc, this._props.PanelWidth(), this._props.PanelHeight()) }), icon: 'map-pin' });
         !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
-        this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+        this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
 
-        this.props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
+        this._props.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' });
 
         !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null;
         !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
@@ -1672,11 +1681,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const options = ContextMenu.Instance.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
-        !this.props.isAnnotationOverlay &&
+        !this._props.isAnnotationOverlay &&
             !Doc.noviceMode &&
             optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
-        this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
-        this.props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
+        this._props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+        this._props.renderDepth && optionItems.push({ description: 'Fit Content Once', event: this.fitContentOnce, icon: 'object-group' });
         if (!Doc.noviceMode) {
             optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
         }
@@ -1687,10 +1696,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
 
     @undoBatch
-    @action
     transcribeStrokes = () => {
-        if (this.props.Document.isGroup && this.props.Document.transcription) {
-            const text = StrCast(this.props.Document.transcription);
+        if (this._props.Document.isGroup && this._props.Document.transcription) {
+            const text = StrCast(this._props.Document.transcription);
             const lines = text.split('\n');
             const height = 30 + 15 * lines.length;
 
@@ -1709,7 +1717,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         visited.add(this.Document);
         showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
         const activeDocs = this.getActiveDocuments();
-        const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+        const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
@@ -1738,7 +1746,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0;
 
     incrementalRender = action(() => {
-        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+        if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath())) {
             const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
             const loadIncrement = 5;
             for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
@@ -1752,28 +1760,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     // which will be a DocumentView.  In this sitation, this freeform views acts as an annotation overlay for
     // the underlying DocumentView and will pan and scoll with the underlying Documen tView.
     @computed get underlayViews() {
-        return this.props.children ? [this.props.children] : [];
+        return this._props.children ? [toJS(this._props.children)] : [];
     }
 
     @computed get placeholder() {
         return (
-            <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}>
-                <span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span>
+            <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}>
+                <span className="collectionfreeformview-placeholderSpan">{this._props.Document.annotationOn ? '' : this._props.Document.title?.toString()}</span>
             </div>
         );
     }
 
     brushedView = () => this._brushedView;
     gridColor = () =>
-        DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)))
+        DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor)))
             .fade(0.5)
             .toString();
     @computed get backgroundGrid() {
         return (
             <div>
                 <CollectionFreeFormBackgroundGrid // bcz : UGHH  don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!?
-                    PanelWidth={this.props.PanelWidth}
-                    PanelHeight={this.props.PanelHeight}
+                    PanelWidth={this._props.PanelWidth}
+                    PanelHeight={this._props.PanelHeight}
                     panX={this.panX}
                     panY={this.panY}
                     color={this.gridColor}
@@ -1795,11 +1803,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
-                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.Document._viewTransition, 'string', null))}
-                viewDefDivClick={this.props.viewDefDivClick}>
+                transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this._props.DocumentView?.()?.Document._viewTransition, 'string', null))}
+                viewDefDivClick={this._props.viewDefDivClick}>
                 {this.underlayViews}
                 {this.contentViews}
-                <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+                <CollectionFreeFormRemoteCursors {...this._props} key="remoteCursors" />
             </CollectionFreeFormPannableContents>
         );
     }
@@ -1807,10 +1815,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         TraceMobx();
         return (
             <MarqueeView
-                {...this.props}
+                {...this._props}
                 ref={this._marqueeViewRef}
                 ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
-                nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
+                nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge}
                 addDocTab={this.addDocTab}
                 slowLoadDocuments={this.slowLoadDocuments}
                 trySelectCluster={this.trySelectCluster}
@@ -1818,25 +1826,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 selectDocuments={this.selectDocuments}
                 addDocument={this.addDocument}
                 addLiveTextDocument={this.addLiveTextBox}
-                getContainerTransform={this.props.ScreenToLocalTransform}
+                getContainerTransform={this._props.ScreenToLocalTransform}
                 getTransform={this.ScreenToLocalXf}
                 panXFieldKey={this.panXFieldKey}
                 panYFieldKey={this.panYFieldKey}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
                 {this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
                 {this.pannableContents}
-                {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
+                {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null}
             </MarqueeView>
         );
     }
 
     @computed get nativeDimScaling() {
-        if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
+        if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 0;
         const nw = this.nativeWidth;
         const nh = this.nativeHeight;
-        const hscale = nh ? this.props.PanelHeight() / nh : 1;
-        const wscale = nw ? this.props.PanelWidth() / nw : 1;
-        return wscale < hscale || (this.props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
+        const hscale = nh ? this._props.PanelHeight() / nh : 1;
+        const wscale = nw ? this._props.PanelWidth() / nw : 1;
+        return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
     }
     nativeDim = () => this.nativeDimScaling;
 
@@ -1853,13 +1861,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             transTime + 1
         );
     };
-    lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
-    lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
-    lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+    lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
+    lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
+    lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15);
     onPassiveWheel = (e: WheelEvent) => {
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
-        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
-        this.props.isSelected() && !scrollable && e.preventDefault();
+        const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
+        this._props.isSelected() && !scrollable && e.preventDefault();
     };
     _oldWheel: any;
     render() {
@@ -1882,16 +1890,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onDragOver={e => e.preventDefault()}
                 onContextMenu={this.onContextMenu}
                 style={{
-                    pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this.props.pointerEvents?.() as any),
+                    pointerEvents: this._props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : (this._props.pointerEvents?.() as any),
                     textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
                     transform: `scale(${this.nativeDimScaling || 1})`,
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
-                    height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
+                    height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
                 }}>
                 {this._lightboxDoc ? (
                     <div style={{ padding: 15, width: '100%', height: '100%' }}>
                         <DocumentView
-                            {...this.props}
+                            {...this._props}
                             Document={this._lightboxDoc}
                             TemplateDataDocument={undefined}
                             PanelWidth={this.lightboxPanelWidth}
@@ -1905,8 +1913,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             childFilters={this.childDocFilters}
                             childFiltersByRanges={this.childDocRangeFilters}
                             searchFilterDocs={this.searchFilterDocs}
-                            isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
-                            isContentActive={this.props.childContentsActive ?? emptyFunction}
+                            isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive}
+                            isContentActive={this._props.childContentsActive ?? emptyFunction}
                             addDocTab={this.addDocTab}
                             ScreenToLocalTransform={this.lightboxScreenToLocal}
                             fitContentsToBox={undefined}
@@ -1916,7 +1924,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ) : (
                     <>
                         {this._firstRender ? this.placeholder : this.marqueeView}
-                        {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+                        {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
                         {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />}
                     </>
                 )}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 330c13e97..9f316bef3 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
@@ -57,6 +57,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
     }
 
+    @observable _props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>;
+    constructor(props: React.PropsWithChildren<SubCollectionViewProps & MarqueeViewProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
     private _commandExecuted = false;
     @observable _lastX: number = 0;
     @observable _lastY: number = 0;
@@ -67,7 +74,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     @observable _lassoFreehand: boolean = false;
 
     @computed get Transform() {
-        return this.props.getTransform();
+        return this._props.getTransform();
     }
     @computed get Bounds() {
         // nda - ternary argument to transformPoint is returning the lower of the downX/Y and lastX/Y and passing in as args x,y
@@ -79,7 +86,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     }
 
     componentDidMount() {
-        this.props.setPreviewCursor?.(this.setPreviewCursor);
+        this._props.setPreviewCursor?.(this.setPreviewCursor);
     }
 
     @action
@@ -101,20 +108,20 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const cm = ContextMenu.Instance;
         const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
         if (e.key === '?') {
-            cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
+            cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
             cm.displayMenu(this._downX, this._downY, undefined, true);
             e.stopPropagation();
-        } else if (e.key === 'u' && this.props.ungroup) {
+        } else if (e.key === 'u' && this._props.ungroup) {
             e.stopPropagation();
-            this.props.ungroup();
+            this._props.ungroup();
         } else if (e.key === ':') {
-            DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y);
+            DocUtils.addDocumentCreatorMenuItems(this._props.addLiveTextDocument, this._props.addDocument || returnFalse, x, y);
 
             cm.displayMenu(this._downX, this._downY, undefined, true);
             e.stopPropagation();
         } else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) {
             e.preventDefault();
-            this.props.selectDocuments(this.props.activeDocuments());
+            this._props.selectDocuments(this._props.activeDocuments());
             e.stopPropagation();
         } else if (e.key === 'q' && e.ctrlKey) {
             e.preventDefault();
@@ -136,7 +143,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 ns.map(line => {
                     const indent = line.search(/\S|$/);
                     const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line });
-                    this.props.addDocument?.(newBox);
+                    this._props.addDocument?.(newBox);
                     ypos += 40 * this.Transform.Scale;
                 });
             })();
@@ -147,8 +154,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 pasteImageBitmap((data: any, error: any) => {
                     error && console.log(error);
                     data &&
-                        Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
-                            this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
+                        Utils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+                            this._props.Document['thumb-frozen'] = new ImageField(returnedfilename);
                         });
                 })
             );
@@ -159,7 +166,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             slide.y = y;
             FormattedTextBox.SelectOnLoad = slide[Id];
             TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined };
-            this.props.addDocument?.(slide);
+            this._props.addDocument?.(slide);
             e.stopPropagation();
         }*/ else if (e.key === 'p' && e.ctrlKey) {
             e.preventDefault();
@@ -170,9 +177,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             })();
             e.stopPropagation();
         } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
-            FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
+            FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : '';
             FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
-            this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
+            this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this._props.xPadding === 0));
             e.stopPropagation();
         }
     };
@@ -203,7 +210,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const file = new File([blob], 'droppedTable', options);
         const loading = Docs.Create.LoadingDocument(file, options);
         DocUtils.uploadFileToDoc(file, {}, loading);
-        this.props.addDocument?.(loading);
+        this._props.addDocument?.(loading);
     }
 
     @action
@@ -214,9 +221,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode;
         // allow marquee if right drag/meta drag, or pan mode
         if (e.button === 2 || e.metaKey || scrollMode === freeformScrollMode.Pan) {
-            this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document);
+            this.setPreviewCursor(e.clientX, e.clientY, true, false, this._props.Document);
             e.preventDefault();
-        } else PreviewCursor.Visible = false;
+        } else PreviewCursor.Instance.Visible = false;
     };
 
     @action
@@ -243,10 +250,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (this._visible) {
             const mselect = this.marqueeSelect();
             if (!e.shiftKey) {
-                SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
+                SelectionManager.DeselectAll(mselect.length ? undefined : this._props.Document);
             }
-            const docs = mselect.length ? mselect : [this.props.Document];
-            this.props.selectDocuments(docs);
+            const docs = mselect.length ? mselect : [this._props.Document];
+            this._props.selectDocuments(docs);
         }
         const hideMarquee = () => {
             this.hideMarquee();
@@ -285,14 +292,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             this._downX = this._lastX = x;
             this._downY = this._lastY = y;
             this._commandExecuted = false;
-            PreviewCursor.Visible = false;
-            PreviewCursor.Doc = undefined;
+            PreviewCursor.Instance.Visible = false;
+            PreviewCursor.Instance.Doc = undefined;
         } else if (drag) {
             this._downX = this._lastX = x;
             this._downY = this._lastY = y;
             this._commandExecuted = false;
-            PreviewCursor.Visible = false;
-            PreviewCursor.Doc = undefined;
+            PreviewCursor.Instance.Visible = false;
+            PreviewCursor.Instance.Doc = undefined;
             this.cleanupInteractions(true);
             document.addEventListener('pointermove', this.onPointerMove, true);
             document.addEventListener('pointerup', this.onPointerUp, true);
@@ -300,10 +307,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         } else {
             this._downX = x;
             this._downY = y;
-            const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
+            const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]);
             if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) {
-                PreviewCursor.Doc = doc;
-                PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge, this.props.slowLoadDocuments);
+                PreviewCursor.Instance.Doc = doc;
+                PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments);
             }
             this.clearSelection();
         }
@@ -311,11 +318,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onClick = (e: React.MouseEvent): void => {
-        if (this.props.pointerEvents?.() === 'none') return;
+        if (this._props.pointerEvents?.() === 'none') return;
         if (Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             if (Doc.ActiveTool === InkTool.None) {
-                if (!this.props.trySelectCluster(e.shiftKey)) {
-                    !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+                if (!this._props.trySelectCluster(e.shiftKey)) {
+                    !DocumentView.ExploreMode && this.setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
                 } else e.stopPropagation();
             }
             // let the DocumentView stopPropagation of this event when it selects this document
@@ -332,16 +339,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     hideMarquee = () => (this._visible = false);
 
     @undoBatch
-    @action
-    delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
+    delete = action((e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
-        selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
+        selected.forEach(doc => (hide ? (doc.hidden = true) : this._props.removeDocument?.(doc)));
 
         this.cleanupInteractions(false);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-    };
+    });
 
     getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt<boolean>) => {
         const newCollection = creator
@@ -366,17 +372,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     });
 
     @undoBatch
-    @action
-    pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+    pileup = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false);
         SelectionManager.DeselectAll();
-        selected.forEach(d => this.props.removeDocument?.(d));
+        selected.forEach(d => this._props.removeDocument?.(d));
         const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
-        this.props.addDocument?.(newCollection);
-        this.props.selectDocuments([newCollection]);
+        this._props.addDocument?.(newCollection);
+        this._props.selectDocuments([newCollection]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-    };
+    });
 
     /**
      * This triggers the TabDocView.PinDoc method which is the universal method
@@ -385,35 +390,32 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
      * This one is unique in that it includes the bounds associated with marquee view.
      */
     @undoBatch
-    @action
-    pinWithView = () => {
-        this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds });
+    pinWithView = action(() => {
+        this._props.pinToPres(this._props.Document, { pinViewport: this.Bounds });
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-    };
+    });
 
     @undoBatch
-    @action
-    collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+    collection = action((e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
         const selected = selection ?? this.marqueeSelect(false);
         const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
         if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
-            this.props.removeDocument?.(selected);
+            this._props.removeDocument?.(selected);
         }
 
         const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
         newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
         newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
         newCollection._currentFrame = activeFrame;
-        this.props.addDocument?.(newCollection);
-        this.props.selectDocuments([newCollection]);
+        this._props.addDocument?.(newCollection);
+        this._props.selectDocuments([newCollection]);
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
-    };
+    });
 
     @undoBatch
-    @action
-    syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+    syntaxHighlight = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false);
         if (e instanceof KeyboardEvent ? e.key === 'i' : true) {
             const inks = selected.filter(s => s.type === DocumentType.INK);
@@ -472,15 +474,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 // }
                 const lines = results.filter((r: any) => r.category === 'line');
                 const text = lines.map((l: any) => l.recognizedText).join('\r\n');
-                this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
+                this._props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
             });
         }
-    };
+    });
 
     @undoBatch
     summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
         const selected = this.marqueeSelect(false).map(d => {
-            this.props.removeDocument?.(d);
+            this._props.removeDocument?.(d);
             d.x = NumCast(d.x) - this.Bounds.left;
             d.y = NumCast(d.y) - this.Bounds.top;
             return d;
@@ -500,8 +502,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
 
         portal.hidden = true;
-        this.props.addDocument?.(portal);
-        this.props.addLiveTextDocument(summary);
+        this._props.addDocument?.(portal);
+        this._props.addLiveTextDocument(summary);
         MarqueeOptionsMenu.Instance.fadeOut(true);
     });
 
@@ -582,17 +584,17 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 (this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc);
             }
         };
-        this.props
+        this._props
             .activeDocuments()
             .filter(doc => !doc.z && !doc._lockedPosition)
             .map(selectFunc);
         if (!selection.length && selectBackgrounds)
-            this.props
+            this._props
                 .activeDocuments()
                 .filter(doc => doc.z === undefined)
                 .map(selectFunc);
         if (!selection.length)
-            this.props
+            this._props
                 .activeDocuments()
                 .filter(doc => doc.z !== undefined)
                 .map(selectFunc);
@@ -601,8 +603,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @computed get marqueeDiv() {
         const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY];
-        const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
-        const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+        const p = this._props.getContainerTransform().transformPoint(cpt[0], cpt[1]);
+        const v = this._lassoFreehand ? [0, 0] : this._props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
         return (
             <div
                 className="marquee"
@@ -610,8 +612,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                     transform: `translate(${p[0]}px, ${p[1]}px)`,
                     width: Math.abs(v[0]),
                     height: Math.abs(v[1]),
-                    color: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
-                    borderColor: lightOrDark(this.props.Document?.backgroundColor ?? 'white'),
+                    color: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
+                    borderColor: lightOrDark(this._props.Document?.backgroundColor ?? 'white'),
                     zIndex: 2000,
                 }}>
                 {' '}
@@ -620,7 +622,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                         <polyline //
                             points={this._lassoPts.reduce((s, pt) => s + pt[0] + ',' + pt[1] + ' ', '')}
                             fill="none"
-                            stroke={lightOrDark(this.props.Document?.backgroundColor ?? 'white')}
+                            stroke={lightOrDark(this._props.Document?.backgroundColor ?? 'white')}
                             strokeWidth="1"
                             strokeDasharray="3"
                         />
@@ -635,19 +637,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
-        if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
+        if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
         (e as any).handlePan = true;
 
         const bounds = this.MarqueeRef?.getBoundingClientRect();
-        if (!this.props.Document._freeform_noAutoPan && !this.props.renderDepth && bounds) {
+        if (!this._props.Document._freeform_noAutoPan && !this._props.renderDepth && bounds) {
             const dragX = e.detail.clientX;
             const dragY = e.detail.clientY;
 
             const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
             const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
             if (deltaX !== 0 || deltaY !== 0) {
-                this.props.Document[this.props.panYFieldKey] = NumCast(this.props.Document[this.props.panYFieldKey]) + deltaY / 2;
-                this.props.Document[this.props.panXFieldKey] = NumCast(this.props.Document[this.props.panXFieldKey]) + deltaX / 2;
+                this._props.Document[this._props.panYFieldKey] = NumCast(this._props.Document[this._props.panYFieldKey]) + deltaY / 2;
+                this._props.Document[this._props.panXFieldKey] = NumCast(this._props.Document[this._props.panXFieldKey]) + deltaX / 2;
             }
         }
         e.stopPropagation();
@@ -661,7 +663,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                     this.MarqueeRef = r;
                 }}
                 style={{
-                    overflow: StrCast(this.props.Document._overflow),
+                    overflow: StrCast(this._props.Document._overflow),
                     cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',
                 }}
                 onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 8daed9746..697a11ccc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -277,7 +277,6 @@ export class CollectionGridView extends CollectionSubView() {
     /**
      * Handles internal drop of Dash documents.
      */
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const savedLayouts = this.savedLayoutList;
         const dropped = de.complete.docDragData?.droppedDocuments;
@@ -292,7 +291,6 @@ export class CollectionGridView extends CollectionSubView() {
     /**
      * Handles external drop of images/PDFs etc from outside Dash.
      */
-    @action
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         this.dropLocation = this.screenToCell(e.clientX, e.clientY);
         super.onExternalDrop(e, {});
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 0ee5f3b40..a9782f699 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,14 +1,14 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -20,7 +20,7 @@ import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
 import { UndoStack } from '../../UndoStack';
 import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
-import { CollectionSubView } from '../CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import './CollectionLinearView.scss';
 
 /**
@@ -39,6 +39,18 @@ export class CollectionLinearView extends CollectionSubView() {
     private _widthDisposer?: IReactionDisposer;
     private _selectedDisposer?: IReactionDisposer;
 
+    _prevProps: SubCollectionViewProps;
+    @override _props: SubCollectionViewProps;
+    constructor(props: SubCollectionViewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentWillUnmount() {
         this._dropDisposer?.();
         this._widthDisposer?.();
@@ -170,28 +182,28 @@ export class CollectionLinearView extends CollectionSubView() {
                 }}>
                 <DocumentView
                     Document={doc}
-                    isContentActive={this.props.isContentActive}
+                    isContentActive={this._props.isContentActive}
                     isDocumentActive={returnTrue}
-                    addDocument={this.props.addDocument}
-                    moveDocument={this.props.moveDocument}
-                    addDocTab={this.props.addDocTab}
+                    addDocument={this._props.addDocument}
+                    moveDocument={this._props.moveDocument}
+                    addDocTab={this._props.addDocTab}
                     pinToPres={emptyFunction}
-                    dragAction={(this.layoutDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
+                    dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
                     rootSelected={this.rootSelected}
-                    removeDocument={this.props.removeDocument}
+                    removeDocument={this._props.removeDocument}
                     ScreenToLocalTransform={docXf}
                     PanelWidth={doc[Width]}
                     PanelHeight={nested || doc._height ? doc[Height] : this.dimension}
-                    renderDepth={this.props.renderDepth + 1}
+                    renderDepth={this._props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
-                    styleProvider={this.props.styleProvider}
+                    styleProvider={this._props.styleProvider}
                     docViewPath={returnEmptyDoclist}
                     whenChildContentsActiveChanged={emptyFunction}
                     bringToFront={emptyFunction}
-                    childFilters={this.props.childFilters}
-                    childFiltersByRanges={this.props.childFiltersByRanges}
-                    searchFilterDocs={this.props.searchFilterDocs}
+                    childFilters={this._props.childFilters}
+                    childFiltersByRanges={this._props.childFiltersByRanges}
+                    searchFilterDocs={this._props.searchFilterDocs}
                     hideResizeHandles={true}
                 />
             </div>
@@ -205,8 +217,8 @@ export class CollectionLinearView extends CollectionSubView() {
 
         const menuOpener = (
             <Toggle
-                text={Cast(this.props.Document.icon, 'string', null)}
-                icon={Cast(this.props.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
+                text={Cast(this.Document.icon, 'string', null)}
+                icon={Cast(this.Document.icon, 'string', null) ? undefined : <FontAwesomeIcon color={SettingsManager.userColor} icon={isExpanded ? 'minus' : 'plus'} />}
                 color={SettingsManager.userColor}
                 background={SettingsManager.userVariantColor}
                 type={Type.TERT}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index e7af91390..037786abf 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -192,7 +192,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     };
 
     @undoBatch
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index ef66e8ce5..bbf3481dd 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -188,7 +188,6 @@ export class CollectionMultirowView extends CollectionSubView() {
     };
 
     @undoBatch
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         let dropInd = -1;
         if (de.complete.docDragData && this._mainCont) {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 029d39f88..9a00595f9 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -68,16 +68,16 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _menuKeys: string[] = [];
     @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
     @observable _colEles: HTMLDivElement[] = [];
-    @observable _displayColumnWidths: number[] | undefined;
-    @observable _columnMenuIndex: number | undefined;
+    @observable _displayColumnWidths: number[] | undefined = undefined;
+    @observable _columnMenuIndex: number | undefined = undefined;
     @observable _newFieldWarning: string = '';
     @observable _makeNewField: boolean = false;
     @observable _newFieldDefault: any = 0;
     @observable _newFieldType: ColumnType = ColumnType.Number;
     @observable _menuValue: string = '';
-    @observable _filterColumnIndex: number | undefined;
+    @observable _filterColumnIndex: number | undefined = undefined;
     @observable _filterSearchValue: string = '';
-    @observable _selectedCell: [Doc, number] | undefined;
+    @observable _selectedCell: [Doc, number] | undefined = undefined;
 
     // target HTMLelement portal for showing a popup menu to edit cell values.
     public get MenuTarget() {
@@ -437,7 +437,6 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     setDropIndex = (index: number) => (this._closestDropIndex = index);
 
-    @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
             const mouseX = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
@@ -473,7 +472,6 @@ export class CollectionSchemaView extends CollectionSubView() {
         return false;
     };
 
-    @action
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
     };
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 0e37198bb..04443b4a7 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -26,7 +26,7 @@ export interface SchemaColumnHeaderProps {
 export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps> {
     @observable _ref: HTMLDivElement | null = null;
 
-    @computed get fieldKey() {
+    get fieldKey() {
         return this.props.columnKeys[this.props.columnIndex];
     }
 
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 53f4a4d51..8d2671a6e 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -34,11 +34,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     bounds = () => this._ref?.getBoundingClientRect();
 
     @computed get schemaView() {
-        return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
+        return this.props.DocumentView?.()._props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
     }
 
     @computed get schemaDoc() {
-        return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document;
+        return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
     }
 
     @computed get rowIndex() {
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bd6d3bad6..af0fe73c3 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import Select, { MenuPlacement } from 'react-select';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import DatePicker from 'react-datepicker';
@@ -95,13 +95,19 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         return { color, textDecoration, fieldProps, cursor, pointerEvents };
     }
 
+    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+    }
+
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.selectedCell();
-        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+        const selected: [Doc, number] | undefined = this._props.selectedCell();
+        return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
 
     @computed get defaultCellContent() {
-        const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props);
 
         return (
             <div
@@ -113,17 +119,17 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                     pointerEvents,
                 }}>
                 <EditableView
-                    oneLine={this.props.oneLine}
-                    allowCRs={this.props.allowCRs}
+                    oneLine={this._props.oneLine}
+                    allowCRs={this._props.allowCRs}
                     contents={<FieldView {...fieldProps} />}
                     editing={this.selected ? undefined : false}
-                    GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+                    GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
                     SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
-                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+                            this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                         }
-                        const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
-                        this.props.finishEdit?.();
+                        const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+                        this._props.finishEdit?.();
                         return ret;
                     }, 'edit schema cell')}
                 />
@@ -132,8 +138,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     get getCellType() {
-        const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
-        const cellValue = this.props.Document[this.props.fieldKey];
+        const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType;
+        const cellValue = this._props.Document[this._props.fieldKey];
         if (cellValue instanceof ImageField) return ColumnType.Image;
         if (cellValue instanceof DateField) return ColumnType.Date;
         if (cellValue instanceof RichTextField) return ColumnType.RTF;
@@ -152,11 +158,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         const cellType: ColumnType = this.getCellType;
         // prettier-ignore
         switch (cellType) {
-            case ColumnType.Image: return <SchemaImageCell {...this.props} />;
-            case ColumnType.Boolean: return <SchemaBoolCell {...this.props} />;
-            case ColumnType.RTF: return <SchemaRTFCell {...this.props} />;
-            case ColumnType.Enumeration: return <SchemaEnumerationCell {...this.props} options={this.props.getFinfo(this.props.fieldKey)?.values?.map(val => val.toString())} />;
-            case ColumnType.Date: // return <SchemaDateCell {...this.props} />;
+            case ColumnType.Image: return <SchemaImageCell {...this._props} />;
+            case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />;
+            case ColumnType.RTF: return <SchemaRTFCell {...this._props} />;
+            case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />;
+            case ColumnType.Date: // return <SchemaDateCell {...this._props} />;
             default:  return this.defaultCellContent;
         }
     }
@@ -165,8 +171,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
         return (
             <div
                 className="schema-table-cell"
-                onPointerDown={action(e => !this.selected && this.props.selectCell(this.props.Document, this.props.col))}
-                style={{ padding: this.props.padding, maxWidth: this.props.maxWidth?.(), width: this.props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
+                onPointerDown={action(e => !this.selected && this._props.selectCell(this._props.Document, this._props.col))}
+                style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
                 {this.content}
             </div>
         );
@@ -176,7 +182,14 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
 // mj: most of this is adapted from old schema code so I'm not sure what it does tbh
 @observer
 export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
-    @observable _previewRef: HTMLImageElement | undefined;
+    @observable _previewRef: HTMLImageElement | undefined = undefined;
+
+    _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
 
     choosePath(url: URL) {
         if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
@@ -188,8 +201,8 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
     }
 
     get url() {
-        const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+        const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+        const alts = DocListCast(this._props.Document[this._props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
         const altpaths = alts
             .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
             .filter(url => url)
@@ -226,10 +239,10 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
     };
 
     render() {
-        const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
-        // let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+        const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio
+        // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px
         // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
-        const height = this.props.rowHeight() ? this.props.rowHeight() - (this.props.padding || 6) * 2 : undefined;
+        const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined;
         const width = height ? height * aspect : undefined; // increase the width of the image if necessary to maintain proportionality
 
         return <img src={this.url} width={width ? width : undefined} height={height} style={{}} draggable="false" onPointerEnter={this.showHoverPreview} onPointerMove={this.moveHoverPreview} onPointerLeave={this.removeHoverPreview} />;
@@ -240,19 +253,26 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
 export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
     @observable _pickingDate: boolean = false;
 
+    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
     @computed get date(): DateField {
         // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
-        return DateCast(this.props.Document[this.props.fieldKey]);
+        return DateCast(this._props.Document[this._props.fieldKey]);
     }
 
     @action
     handleChange = (date: any) => {
         // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
         // if (script.compiled) {
-        //     this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+        //     this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
         // } else {
         // ^ DateCast is always undefined for some reason, but that is what the field should be set to
-        this.props.Document[this.props.fieldKey] = new DateField(date as Date);
+        this._props.Document[this._props.fieldKey] = new DateField(date as Date);
         //}
     };
 
@@ -262,13 +282,20 @@ export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
 }
 @observer
 export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
+    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.selectedCell();
-        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+        const selected: [Doc, number] | undefined = this._props.selectedCell();
+        return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
     selectedFunc = () => this.selected;
     render() {
-        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
         fieldProps.isContentActive = this.selectedFunc;
         return (
             <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}>
@@ -279,35 +306,42 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
 }
 @observer
 export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
+    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.selectedCell();
-        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+        const selected: [Doc, number] | undefined = this._props.selectedCell();
+        return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
     render() {
-        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
         return (
             <div className="schemaBoolCell" style={{ display: 'flex', color, textDecoration, cursor, pointerEvents }}>
                 <input
                     style={{ marginRight: 4 }}
                     type="checkbox"
-                    checked={BoolCast(this.props.Document[this.props.fieldKey])}
+                    checked={BoolCast(this._props.Document[this._props.fieldKey])}
                     onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => {
                         if ((value?.nativeEvent as any).shiftKey) {
-                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+                            this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                         }
-                        KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
+                        KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString());
                     })}
                 />
                 <EditableView
                     contents={<FieldView {...fieldProps} />}
                     editing={this.selected ? undefined : false}
-                    GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+                    GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
                     SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
                         if (shiftDown && enterKey) {
-                            this.props.setColumnValues(this.props.fieldKey.replace(/^_/, ''), value);
+                            this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value);
                         }
-                        const set = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value);
-                        this.props.finishEdit?.();
+                        const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value);
+                        this._props.finishEdit?.();
                         return set;
                     })}
                 />
@@ -317,13 +351,20 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
 }
 @observer
 export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps> {
+    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
+    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
     @computed get selected() {
-        const selected: [Doc, number] | undefined = this.props.selectedCell();
-        return this.props.isRowActive() && selected?.[0] === this.props.Document && selected[1] === this.props.col;
+        const selected: [Doc, number] | undefined = this._props.selectedCell();
+        return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col;
     }
     render() {
-        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props);
-        const options = this.props.options?.map(facet => ({ value: facet, label: facet }));
+        const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+        const options = this._props.options?.map(facet => ({ value: facet, label: facet }));
         return (
             <div className="schemaSelectionCell" style={{ color, textDecoration, cursor, pointerEvents }}>
                 <div style={{ width: '100%' }}>
@@ -357,17 +398,17 @@ export class SchemaEnumerationCell extends React.Component<SchemaTableCellProps>
                                 ...base,
                                 left: 0,
                                 top: 0,
-                                transform: `translate(${this.props.transform().TranslateX}px, ${this.props.transform().TranslateY}px)`,
-                                width: Number(base.width) * this.props.transform().Scale,
+                                transform: `translate(${this._props.transform().TranslateX}px, ${this._props.transform().TranslateY}px)`,
+                                width: Number(base.width) * this._props.transform().Scale,
                                 zIndex: 9999,
                             }),
                         }}
-                        menuPortalTarget={this.props.menuTarget}
+                        menuPortalTarget={this._props.menuTarget}
                         menuPosition={'absolute'}
-                        placeholder={StrCast(this.props.Document[this.props.fieldKey], 'select...')}
+                        placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
                         options={options}
                         isMulti={false}
-                        onChange={val => KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
+                        onChange={val => KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)}
                     />
                 </div>
             </div>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index de29d8602..dcd3bffac 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -42,14 +42,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
         if (checkResult) {
             const selView = selectedViews.lastElement();
             const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
         }
         selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
         selectedViews.forEach(dv => {
             const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
-            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+            const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
             const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
             if (contentFrameNumber !== undefined) {
                 const obj: { [key: string]: Opt<string> } = {};
@@ -280,7 +280,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
                     return d;
                 })
             );
-            ffView.props.removeDocument?.(selected);
+            ffView._props.removeDocument?.(selected);
             // TODO: nda - this is the code to actually get a new grouped collection
             const newCollection = marqViewRef?.getCollection(selected, undefined, true);
             if (newCollection) {
@@ -289,7 +289,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
             }
 
             // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
-            newCollection && ffView.props.addDocument?.(newCollection);
+            newCollection && ffView._props.addDocument?.(newCollection);
             // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
             ffView.unprocessedDocs = [];
 
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 3d9cf1893..91d812f35 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
 import { LinkManager } from '../../util/LinkManager';
@@ -23,6 +23,10 @@ interface Props {
 export class LinkMenu extends React.Component<Props> {
     _editorRef = React.createRef<HTMLDivElement>();
     @observable _linkMenuRef = React.createRef<HTMLDivElement>();
+    constructor(props: Props) {
+        super(props);
+        makeObservable(this);
+    }
 
     clear = () => this.props.clearLinkEditor?.();
 
@@ -47,7 +51,7 @@ export class LinkMenu extends React.Component<Props> {
      */
     renderAllGroups = (groups: Map<string, Array<Doc>>): Array<JSX.Element> => {
         const linkItems = Array.from(groups.entries()).map(group => (
-            <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.props.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
+            <LinkMenuGroup key={group[0]} itemHandler={this.props.itemHandler} docView={this.props.docView} sourceDoc={this.props.docView.Document} group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} />
         ));
 
         return linkItems.length ? linkItems : this.props.style ? [] : [<p key="none">No links have been created yet. Drag the linking button onto another document to create a link.</p>];
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 12db7fa4b..858cc2e5e 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,12 +1,12 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
 import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, copyProps } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
@@ -52,9 +52,19 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
 @observer
 export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     private _drag = React.createRef<HTMLDivElement>();
-
     _editRef = React.createRef<HTMLDivElement>();
 
+    _prevProps: React.PropsWithChildren<LinkMenuItemProps>;
+    @observable _props: React.PropsWithChildren<LinkMenuItemProps>;
+    constructor(props: React.PropsWithChildren<LinkMenuItemProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable private _showMore: boolean = false;
     @action toggleShowMore(e: React.PointerEvent) {
         e.stopPropagation();
@@ -62,12 +72,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     @computed get sourceAnchor() {
-        const ldoc = this.props.linkDoc;
-        if (this.props.sourceDoc !== ldoc.link_anchor_1 && this.props.sourceDoc !== ldoc.link_anchor_2) {
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
-            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
+        const ldoc = this._props.linkDoc;
+        if (this._props.sourceDoc !== ldoc.link_anchor_1 && this._props.sourceDoc !== ldoc.link_anchor_2) {
+            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_1).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_1);
+            if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.link_anchor_2).annotationOn), this._props.sourceDoc)) return DocCast(ldoc.link_anchor_2);
         }
-        return this.props.sourceDoc;
+        return this._props.sourceDoc;
     }
 
     onEdit = (e: React.PointerEvent) => {
@@ -75,24 +85,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             this,
             e,
             e => {
-                const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'embed');
+                const dragData = new DragManager.DocumentDragData([this._props.linkDoc], 'embed');
                 dragData.dropPropertiesToRemove = ['hidden'];
                 DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
                 return true;
             },
             emptyFunction,
             action(() => {
-                const trail = DocCast(this.props.docView.Document.presentationTrail);
+                const trail = DocCast(this._props.docView.Document.presentationTrail);
                 if (trail) {
                     Doc.ActivePresentation = trail;
                     DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
                 } else {
-                    SelectionManager.SelectView(this.props.docView, false);
-                    LinkManager.currentLink = this.props.linkDoc === LinkManager.currentLink ? undefined : this.props.linkDoc;
+                    SelectionManager.SelectView(this._props.docView, false);
+                    LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc;
                     LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
 
-                    if ((SettingsManager.propertiesWidth ?? 0) < 100) {
-                        setTimeout(action(() => (SettingsManager.propertiesWidth = 250)));
+                    if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+                        setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
                     }
                 }
             })
@@ -106,43 +116,43 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             e => {
                 const eleClone: any = this._drag.current!.cloneNode(true);
                 eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`;
-                StartLinkTargetsDrag(eleClone, this.props.docView, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]);
-                this.props.clearLinkEditor?.();
+                StartLinkTargetsDrag(eleClone, this._props.docView, e.x, e.y, this._props.sourceDoc, [this._props.linkDoc]);
+                this._props.clearLinkEditor?.();
                 return true;
             },
             emptyFunction,
             () => {
-                this.props.clearLinkEditor?.();
-                if (this.props.itemHandler) {
-                    this.props.itemHandler?.(this.props.linkDoc);
+                this._props.clearLinkEditor?.();
+                if (this._props.itemHandler) {
+                    this._props.itemHandler?.(this._props.linkDoc);
                 } else {
                     const focusDoc =
-                        Cast(this.props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this.props.sourceDoc
-                            ? Cast(this.props.linkDoc.link_anchor_1, Doc, null)
-                            : Cast(this.props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this.props.sourceDoc
-                            ? Cast(this.props.linkDoc.link_anchor_12, Doc, null)
-                            : undefined;
-
-                    if (focusDoc) this.props.docView.props.focus(focusDoc, { instant: true });
-                    LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, false);
+                        Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc
+                            ? Cast(this._props.linkDoc.link_anchor_1, Doc, null)
+                            : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc
+                              ? Cast(this._props.linkDoc.link_anchor_12, Doc, null)
+                              : undefined;
+
+                    if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true });
+                    LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false);
                 }
             }
         );
     };
 
-    deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc))));
+    deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc))));
     @observable _hover = false;
     render() {
-        const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp;
+        const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp;
 
-        const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title;
+        const title = StrCast(this._props.destinationDoc.title).length > 18 ? StrCast(this._props.destinationDoc.title).substr(0, 14) + '...' : this._props.destinationDoc.title;
 
         const source =
-            this.props.sourceDoc.type === DocumentType.RTF
-                ? this.props.linkDoc.storedText
-                    ? StrCast(this.props.linkDoc.storedText).length > 17
-                        ? StrCast(this.props.linkDoc.storedText).substr(0, 18)
-                        : this.props.linkDoc.storedText
+            this._props.sourceDoc.type === DocumentType.RTF
+                ? this._props.linkDoc.storedText
+                    ? StrCast(this._props.linkDoc.storedText).length > 17
+                        ? StrCast(this._props.linkDoc.storedText).substr(0, 18)
+                        : this._props.linkDoc.storedText
                     : undefined
                 : undefined;
 
@@ -154,7 +164,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                 style={{
                     fontSize: this._hover ? 'larger' : undefined,
                     fontWeight: this._hover ? 'bold' : undefined,
-                    background: LinkManager.currentLink === this.props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+                    background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
                 }}>
                 <div className="linkMenu-item-content expand-two">
                     <div
@@ -172,12 +182,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             className="linkMenu-text"
                             onPointerLeave={LinkDocPreview.Clear}
                             onPointerEnter={e =>
-                                this.props.linkDoc &&
-                                this.props.clearLinkEditor &&
+                                this._props.linkDoc &&
+                                this._props.clearLinkEditor &&
                                 LinkDocPreview.SetLinkInfo({
-                                    docProps: this.props.docView.props,
-                                    linkSrc: this.props.sourceDoc,
-                                    linkDoc: this.props.linkDoc,
+                                    docProps: this._props.docView._props,
+                                    linkSrc: this._props.sourceDoc,
+                                    linkDoc: this._props.linkDoc,
                                     showHeader: false,
                                     location: [(this._drag.current?.getBoundingClientRect().left ?? 100) + 40, (this._drag.current?.getBoundingClientRect().top ?? e.clientY) + 25],
                                     noPreview: false,
@@ -194,10 +204,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                     <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
                                 </div>
                                 <p className="linkMenu-destination-title">
-                                    {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
+                                    {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
                                 </p>
                             </div>
-                            {!this.props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this.props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
+                            {!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
                         </div>
 
                         <div className="linkMenu-item-buttons">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1fb26c99b..0c671f7e3 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,14 +1,14 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { DateField } from '../../../fields/DateField';
 import { Doc } from '../../../fields/Doc';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -49,10 +49,22 @@ export enum media_state {
 }
 
 @observer
-export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(AudioBox, fieldKey);
     }
+
+    _prevProps: React.PropsWithChildren<FieldViewProps>;
+    @override _props: React.PropsWithChildren<FieldViewProps>;
+    constructor(props: React.PropsWithChildren<FieldViewProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
     public static Enabled = false;
 
     static topControlsHeight = 30; // height of upper controls above timeline
@@ -68,7 +80,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     _stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
     _play: any = null; // timeout for playback
 
-    @observable _stackedTimeline: CollectionStackedTimeline | null | undefined; // CollectionStackedTimeline ref
+    @observable _stackedTimeline: CollectionStackedTimeline | null | undefined = undefined; // CollectionStackedTimeline ref
     @observable _finished: boolean = false; // has playback reached end of clip
     @observable _volume: number = 1;
     @observable _muted: boolean = false;
@@ -84,7 +96,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // if you get rid of it and set the value to 0 the timeline and waveform will set their bounds incorrectly
 
     @computed get miniPlayer() {
-        return this.props.PanelHeight() < 50;
+        return this._props.PanelHeight() < 50;
     } // used to collapse timeline when node is shrunk
     @computed get links() {
         return LinkManager.Links(this.dataDoc);
@@ -94,7 +106,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
     @computed get path() {
         // returns the path of the audio file
-        const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || '';
+        const path = Cast(this.Document[this.fieldKey], AudioField, null)?.url.href || '';
         return path === nullAudio ? '' : path;
     }
     set mediaState(value) {
@@ -115,7 +127,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @action
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         if (this.path) {
             this.mediaState = media_state.Paused;
             this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -142,7 +154,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                   this.Document,
                   this.dataDoc,
                   this.annotationKey,
-                  this._ele?.currentTime || Cast(this.props.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
+                  this._ele?.currentTime || Cast(this.Document._layout_currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined),
                   undefined,
                   undefined,
                   addAsAnnotation
@@ -186,13 +198,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 this._ele.play();
                 this.mediaState = media_state.Playing;
                 this.addCurrentlyPlaying();
-                this._play = setTimeout(() => {
-                    // need to keep track of if end of clip is reached so on next play, clip restarts
-                    if (fullPlay) this._finished = true;
-                    // removes from currently playing if playback has reached end of range marker
-                    else this.removeCurrentlyPlaying();
-                    this.Pause();
-                }, (end - start) * 1000);
+                this._play = setTimeout(
+                    () => {
+                        // need to keep track of if end of clip is reached so on next play, clip restarts
+                        if (fullPlay) this._finished = true;
+                        // removes from currently playing if playback has reached end of range marker
+                        else this.removeCurrentlyPlaying();
+                        this.Pause();
+                    },
+                    (end - start) * 1000
+                );
             } else {
                 this.Pause();
             }
@@ -202,7 +217,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -212,7 +227,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
@@ -240,7 +255,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._recorder.ondataavailable = async (e: any) => {
             const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
             if (!(result instanceof Error)) {
-                this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
+                this.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
             }
         };
         this._recordStart = new Date().getTime();
@@ -373,7 +388,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
                     Doc.AddToMyOverlay(newDoc);
                 } else {
-                    this.props.addDocument?.(newDoc);
+                    this._props.addDocument?.(newDoc);
                 }
             }),
             false
@@ -449,9 +464,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     };
 
     @action
-    timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
+    timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
 
-    timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
+    timelineScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
 
     setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
 
@@ -460,8 +475,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     isActiveChild = () => this._isAnyChildContentActive;
 
     // timeline dimensions
-    timelineWidth = () => this.props.PanelWidth();
-    timelineHeight = () => this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
+    timelineWidth = () => this._props.PanelWidth();
+    timelineHeight = () => this._props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight);
 
     // ends trim, hides trim controls and displays new clip
     @undoBatch
@@ -555,7 +570,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this._dropDisposer = DragManager.MakeDropTarget(
                 r,
                 (e, de) => {
-                    const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+                    const [xp, yp] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
                     de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc,
@@ -597,7 +612,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             <div
                 className="audiobox-file"
                 style={{
-                    pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? 'all' : 'none',
+                    pointerEvents: this._isAnyChildContentActive || this._props.isContentActive() ? 'all' : 'none',
                     flexDirection: this.miniPlayer ? 'row' : 'column',
                     justifyContent: this.miniPlayer ? 'flex-start' : 'space-between',
                 }}>
@@ -670,7 +685,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <div className="audiobox-timecodes">
                     <div className="timecode-current">{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._layout_currentTimecode) - NumCast(this.timeline.clipStart)))}</div>
                     {this.miniPlayer ? (
-                        <div>/</div>
+                        <div />
                     ) : (
                         <div className="bottom-controls-middle">
                             <FontAwesomeIcon icon="search-plus" />
@@ -699,13 +714,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (
             <CollectionStackedTimeline
                 ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
-                {...this.props}
+                {...this._props}
                 CollectionFreeFormDocumentView={undefined}
                 dataFieldKey={this.fieldKey}
                 fieldKey={this.annotationKey}
                 dictationKey={this.fieldKey + '_dictation'}
                 mediaPath={this.path}
-                renderDepth={this.props.renderDepth + 1}
+                renderDepth={this._props.renderDepth + 1}
                 startTag={'_timecodeToShow' /* audioStart */}
                 endTag={'_timecodeToHide' /* audioEnd */}
                 bringToFront={emptyFunction}
@@ -719,7 +734,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 ScreenToLocalTransform={this.timelineScreenToLocal}
                 Play={this.Play}
                 Pause={this.Pause}
-                isContentActive={this.props.isContentActive}
+                isContentActive={this._props.isContentActive}
                 isAnyChildContentActive={this.isAnyChildContentActive}
                 playLink={this.playLink}
                 PanelWidth={this.timelineWidth}
@@ -734,7 +749,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (
             <audio
                 ref={this.setRef}
-                className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}
+                className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}
                 onLoadedData={action(e => this._ele?.duration && this._ele?.duration !== Infinity && (this.dataDoc[this.fieldKey + '_duration'] = this._ele.duration))}>
                 <source src={this.path} type="audio/mpeg" />
                 Not supported.
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 624f28413..8e7a6914f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, makeObservable, reaction, runInAction, override } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
@@ -6,7 +6,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { numberRange, OmitKeys } from '../../../Utils';
+import { copyProps, numberRange, OmitKeys } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -37,6 +37,13 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
 }
 @observer
 export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
+    _prevProps: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>;
+    @override _props: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>;
+    constructor(props: React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
     @observable X = this.props.x;
     @observable Y = this.props.y;
     @observable Z = this.props.z;
@@ -64,11 +71,11 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     w_X = ()               => this.X; // prettier-ignore
     w_Y = ()               => this.Y; // prettier-ignore
     w_Z = ()               => this.Z; // prettier-ignore
-    w_ZIndex = ()          => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore
-    w_Rotation = ()        => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore
+    w_ZIndex = ()          => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
+    w_Rotation = ()        => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
     w_Opacity = ()         => this.Opacity; // prettier-ignore
-    w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore
-    w_Color = ()           => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore
+    w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
+    w_Color = ()           => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
     w_Highlight = ()       => this.Highlight; // prettier-ignore
     w_Width = ()           => this.Width; // prettier-ignore
     w_Height = ()          => this.Height; // prettier-ignore
@@ -76,17 +83,18 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     w_Transition = ()      => this.Transition; // prettier-ignore
     w_DataTransition = ()  => this.DataTransition; // prettier-ignore
 
-    PanelWidth = ()        => this.props.autoDim ? this.props.PanelWidth?.() : this.Width; // prettier-ignore
-    PanelHeight = ()       => this.props.autoDim ? this.props.PanelHeight?.() : this.Height; // prettier-ignore
-    @action
+    PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
+    PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
+
     componentDidUpdate() {
-        this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower]));
+        copyProps(this);
+        this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
     }
     render() {
         const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
         return (
             <CollectionFreeFormDocumentView
-                {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
+                {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
                 {...layoutProps}
                 PanelWidth={this.PanelWidth}
                 PanelHeight={this.PanelHeight}
@@ -115,7 +123,7 @@ export interface CollectionFreeFormDocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps & { fieldKey: string }>() {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
     get displayName() { // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
@@ -135,33 +143,46 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
     public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
 
+    _props: React.PropsWithChildren<CollectionFreeFormDocumentViewProps & DocumentViewProps>;
+    constructor(props: React.PropsWithChildren<CollectionFreeFormDocumentViewProps & DocumentViewProps>) {
+        super(props);
+        this._props = Object.assign({}, props);
+        makeObservable(this);
+    }
+
     get CollectionFreeFormView() {
-        return this.props.CollectionFreeFormView;
+        return this._props.CollectionFreeFormView;
     }
 
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
-                case StyleProp.Opacity:         return this.props.w_Opacity(); // only change the opacity for this specific document, not its children
-                case StyleProp.BackgroundColor: return this.props.w_BackgroundColor();
-                case StyleProp.Color:           return this.props.w_Color();
+                case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
+                case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
+                case StyleProp.Color:           return this._props.w_Color();
             } // prettier-ignore
         }
-        return this.props.styleProvider?.(doc, props, property);
+        return this._props.styleProvider?.(doc, props, property);
     };
 
     public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
-        return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
-            p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
-            return p;
-        }, {} as { [val: string]: Opt<number> });
+        return CollectionFreeFormDocumentView.animFields.reduce(
+            (p, val) => {
+                p[val.key] = Cast(doc[`${val.key}_indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
+                return p;
+            },
+            {} as { [val: string]: Opt<number> }
+        );
     }
 
     public static getStringValues(doc: Doc, time: number) {
-        return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
-            p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
-            return p;
-        }, {} as { [val: string]: Opt<string> });
+        return CollectionFreeFormDocumentView.animStringFields.reduce(
+            (p, val) => {
+                p[val] = Cast(doc[`${val}_indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
+                return p;
+            },
+            {} as { [val: string]: Opt<string> }
+        );
     }
 
     public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
@@ -213,7 +234,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     @action public float = () => {
         const topDoc = this.Document;
-        const containerDocView = this.props.docViewPath().lastElement();
+        const containerDocView = this._props.docViewPath().lastElement();
         const screenXf = containerDocView?.screenToLocalTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
@@ -222,8 +243,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.z = 0;
                 topDoc.x = spt[0];
                 topDoc.y = spt[1];
-                this.props.removeDocument?.(topDoc);
-                this.props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
+                this._props.removeDocument?.(topDoc);
+                this._props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                 const fpt = screenXf.transformPoint(spt[0], spt[1]);
@@ -236,15 +257,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     };
 
     nudge = (x: number, y: number) => {
-        const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y);
-        this.props.Document.x = this.props.w_X() + locX;
-        this.props.Document.y = this.props.w_Y() + locY;
+        const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+        this._props.Document.x = this._props.w_X() + locX;
+        this._props.Document.y = this._props.w_Y() + locY;
     };
     screenToLocalTransform = () =>
-        this.props
+        this._props
             .ScreenToLocalTransform()
-            .translate(-this.props.w_X(), -this.props.w_Y())
-            .rotateDeg(-(this.props.w_Rotation?.() || 0));
+            .translate(-this._props.w_X(), -this._props.w_Y())
+            .rotateDeg(-(this._props.w_Rotation?.() || 0));
     returnThis = () => this;
 
     /// this indicates whether the doc view is activated because of its relationshop to a group
@@ -255,25 +276,25 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     isGroupActive = () => {
         if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
         const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
-        return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
+        return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
     };
     public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
     render() {
         TraceMobx();
-        const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+        const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
         return (
             <div
                 className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
                 style={{
-                    width: this.props.PanelWidth(),
-                    height: this.props.PanelHeight(),
-                    transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`,
-                    transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()),
-                    zIndex: this.props.w_ZIndex?.(),
-                    display: this.props.w_Width?.() ? undefined : 'none',
+                    width: this._props.PanelWidth(),
+                    height: this._props.PanelHeight(),
+                    transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
+                    transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
+                    zIndex: this._props.w_ZIndex?.(),
+                    display: this._props.w_Width?.() ? undefined : 'none',
                 }}>
-                {this.props.RenderCutoffProvider(this.props.Document) ? (
-                    <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} />
+                {this._props.RenderCutoffProvider(this._props.Document) ? (
+                    <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
                 ) : (
                     <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
                 )}
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
deleted file mode 100644
index d5f2a7ec7..000000000
--- a/src/client/views/nodes/ColorBox.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-.colorBox-container, .colorBox-container-interactive {
-     width:100%;
-     height:100%;
-     position: relative;
-     pointer-events: none;
-     transform-origin: top left;
-     
-    .sketch-picker {
-        div {
-            cursor: crosshair;
-        }
-        .flexbox-fix {
-            cursor: pointer;
-            div {
-                cursor:pointer;
-            }
-        }
-    }
-}
-.colorBox-container-interactive {
-    pointer-events:all;
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
deleted file mode 100644
index b4ba51814..000000000
--- a/src/client/views/nodes/ColorBox.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import * as React from 'react';
-import { action } from 'mobx';
-import { observer } from 'mobx-react';
-import { ColorResult, SketchPicker } from 'react-color';
-import { Doc } from '../../../fields/Doc';
-import { InkTool } from '../../../fields/InkField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { DashColor } from '../../../Utils';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
-import './ColorBox.scss';
-import { FieldView, FieldViewProps } from './FieldView';
-import { RichTextMenu } from './formattedText/RichTextMenu';
-
-@observer
-export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() {
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(ColorBox, fieldKey);
-    }
-
-    @undoBatch
-    @action
-    static switchColor(color: ColorResult) {
-        SetActiveInkColor(color.hex);
-
-        SelectionManager.Views().map(view => {
-            const targetDoc =
-                view.props.Document.dragFactory instanceof Doc
-                    ? view.props.Document.dragFactory
-                    : view.props.Document.layout instanceof Doc
-                    ? view.props.Document.layout
-                    : view.props.Document.isTemplateForField
-                    ? view.props.Document
-                    : Doc.GetProto(view.props.Document);
-            if (targetDoc) {
-                if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {
-                    // this situation typically occurs when you have a link dot
-                    targetDoc.backgroundColor = color.hex; // bcz: don't know how to change the color of an inline template...
-                } else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== '') {
-                    Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + '-color'] = color.hex;
-                } else {
-                    Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ''); // '_backgroundColor' is template specific.  'backgroundColor' would apply to all templates, but has no UI at the moment
-                }
-            }
-        });
-    }
-
-    render() {
-        const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.Document._height), this.props.PanelWidth() / NumCast(this.Document._width));
-        return (
-            <div
-                className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`}
-                onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
-                onClick={e => e.stopPropagation()}
-                style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }}>
-                <SketchPicker
-                    onChange={c => Doc.ActiveTool === InkTool.None && ColorBox.switchColor(c)}
-                    color={StrCast(SelectionManager.Views()?.[0]?.Document?._backgroundColor, ActiveInkColor())}
-                    presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
-                />
-
-                <div style={{ width: this.props.PanelWidth() / scaling, display: 'flex', paddingTop: '10px' }}>
-                    <div> {ActiveInkWidth()}</div>
-                    <input
-                        type="range"
-                        defaultValue={ActiveInkWidth()}
-                        min={1}
-                        max={100}
-                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
-                            SetActiveInkWidth(e.target.value);
-                            SelectionManager.Views()
-                                .filter(i => StrCast(i.Document.type) === DocumentType.INK)
-                                .map(i => (i.Document.stroke_width = Number(e.target.value)));
-                        }}
-                    />
-                </div>
-            </div>
-        );
-    }
-}
-
-ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) {
-    return DashColor(c1).mix(DashColor(c2), weight);
-});
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 0e766e5f0..33be87f46 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -77,7 +77,7 @@ export class TableBox extends React.Component<TableBoxProps> {
     };
 
     @computed get viewScale() {
-        return this.props.docView?.()?.props.ScreenToLocalTransform().Scale || 1;
+        return this.props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
     }
     @computed get rowHeight() {
         console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
@@ -162,8 +162,6 @@ export class TableBox extends React.Component<TableBoxProps> {
     };
 
     render() {
-        console.log(this.endID);
-        trace();
         if (this._tableData.length > 0) {
             return (
                 <div
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 54cfba506..82d346206 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable, observable, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
@@ -18,7 +18,6 @@ import { SearchBox } from '../search/SearchBox';
 import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
 import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
 import { AudioBox } from './AudioBox';
-import { ColorBox } from './ColorBox';
 import { ComparisonBox } from './ComparisonBox';
 import { DataVizBox } from './DataVizBox/DataVizBox';
 import { DocumentViewProps } from './DocumentView';
@@ -46,7 +45,7 @@ import { PresBox } from './trails/PresBox';
 import { VideoBox } from './VideoBox';
 import { WebBox } from './WebBox';
 import * as React from 'react';
-import XRegExp = require('xregexp');
+import * as XRegExp from 'xregexp';
 
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -123,30 +122,42 @@ export class DocumentContentsView extends React.Component<
             layout_fieldKey: string;
         }
 > {
+    @observable _props!: DocumentViewProps &
+        FieldViewProps & {
+            setHeight?: (height: number) => void;
+            layout_fieldKey: string;
+        };
+    constructor(props: any) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate(prevProps: Readonly<DocumentViewProps & { setHeight?: ((height: number) => void) | undefined; layout_fieldKey: string }>, prevState: Readonly<{}>, snapshot?: any): void {
+        // untracked(() => (this._props = this.props));
+        //   Object.keys(prevProps).forEach(pkey => (prevProps as any)[pkey] !== (this._props as any)[pkey] && console.log(pkey + ' ' + (prevProps as any)[pkey] + ' ' + (this._props as any)[pkey]));
+    }
+
     @computed get layout(): string {
         TraceMobx();
-        if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString;
+        if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
         if (!this.layoutDoc) return '<p>awaiting layout</p>';
-        if (this.props.layout_fieldKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString());
-        const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layout_fieldKey ? this.props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
-        if (layout === undefined) return this.props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
+        if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+        const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+        if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
         if (typeof layout === 'string') return layout;
         return '<p>Loading layout</p>';
     }
 
-    componentDidUpdate(prevProps: Readonly<DocumentViewProps & { setHeight?: ((height: number) => void) | undefined; layout_fieldKey: string }>, prevState: Readonly<{}>, snapshot?: any): void {
-        Object.keys(prevProps).forEach(pkey => (prevProps as any)[pkey] !== (this.props as any)[pkey] && console.log(pkey + ' ' + (prevProps as any)[pkey] + ' ' + (this.props as any)[pkey]));
-    }
-
     get layoutDoc() {
         // bcz: replaced this with below : is it correct?  change was made to accommodate passing fieldKey's from a layout script
-        // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
+        // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
         const template: Doc =
-            this.props.LayoutTemplate?.() ||
-            (this.props.LayoutTemplateString && this.props.Document) ||
-            (this.props.layout_fieldKey && StrCast(this.props.Document[this.props.layout_fieldKey]) && this.props.Document) ||
-            Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined);
-        return Doc.expandTemplateLayout(template, this.props.Document);
+            this._props.LayoutTemplate?.() ||
+            (this._props.LayoutTemplateString && this._props.Document) ||
+            (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
+            Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+        return Doc.expandTemplateLayout(template, this._props.Document);
     }
 
     CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
@@ -165,10 +176,10 @@ export class DocumentContentsView extends React.Component<
             'onPointerDown',
             'onPointerUp',
         ];
-        const templateDataDoc = this.props.TemplateDataDocument ?? (this.layoutDoc !== this.props.Document ? this.props.Document[DocData] : undefined);
+        const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
         const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
-            ...this.props,
-            Document: this.layoutDoc ?? this.props.Document,
+            ...this._props,
+            Document: this.layoutDoc ?? this._props.Document,
             TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc,
             onClick: onClick as any as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag
             onInput: onInput as any as React.FormEventHandler,
@@ -181,7 +192,7 @@ export class DocumentContentsView extends React.Component<
     }
 
     // componentWillUpdate(oldProps: any, newState: any) {
-    //     // console.log("willupdate", oldProps, this.props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
+    //     // console.log("willupdate", oldProps, this._props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed
     // }
 
     @computed get renderData() {
@@ -190,13 +201,13 @@ export class DocumentContentsView extends React.Component<
 
         // replace code content with a script  >{content}<   as in  <HTMLdiv>{this.title}</HTMLdiv>
         const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => {
-            return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix;
+            return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix;
         };
         layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer);
 
         // replace HTML<tag> with corresponding HTML tag as in:  <HTMLdiv> becomes  <HTMLtag Document={props.Document} htmltag='div'>
         const replacer2 = (match: any, p1: string, offset: any, string: any) => {
-            return `<HTMLtag Document={props.Document} scaling='${this.props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
+            return `<HTMLtag Document={props.Document} scaling='${this._props.NativeDimScaling?.() || 1}' htmltag='${p1}'`;
         };
         layoutFrame = layoutFrame.replace(/<HTML([a-zA-Z0-9_-]+)/g, replacer2);
 
@@ -228,7 +239,7 @@ export class DocumentContentsView extends React.Component<
         TraceMobx();
         const { bindings, layoutFrame } = this.renderData;
 
-        return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
+        return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : (
             <ObserverJsxParser
                 key={42}
                 blacklistedAttrs={emptyPath}
@@ -256,7 +267,6 @@ export class DocumentContentsView extends React.Component<
                     PresElementBox,
                     SearchBox,
                     FunctionPlotBox,
-                    ColorBox,
                     DashWebRTCVideo,
                     LinkAnchorBox,
                     InkingStroke,
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 90b044374..19b43fd52 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,10 +1,10 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
+import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { Hypothesis } from '../../util/HypothesisUtils';
@@ -16,7 +16,6 @@ import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { PinProps } from './trails';
 import * as React from 'react';
-import * as _ from 'lodash';
 
 interface DocumentLinksButtonProps {
     View: DocumentView;
@@ -29,25 +28,51 @@ interface DocumentLinksButtonProps {
     scaling?: () => number; // how uch doc is scaled so that link buttons can invert it
     hideCount?: () => boolean;
 }
+
+export class DocButtonState {
+    @observable public StartLink: Doc | undefined = undefined; //origin's Doc, if defined
+    @observable public StartLinkView: DocumentView | undefined = undefined;
+    @observable public AnnotationId: string | undefined = undefined;
+    @observable public AnnotationUri: string | undefined = undefined;
+    @observable public LinkEditorDocView: DocumentView | undefined = undefined;
+    public static _instance: DocButtonState | undefined;
+    public static get Instance() {
+        return DocButtonState._instance ?? (DocButtonState._instance = new DocButtonState());
+    }
+    constructor() {
+        makeObservable(this);
+    }
+}
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
-    @observable public static StartLink: Doc | undefined; //origin's Doc, if defined
-    @observable public static StartLinkView: DocumentView | undefined;
-    @observable public static AnnotationId: string | undefined;
-    @observable public static AnnotationUri: string | undefined;
-    @observable public static LinkEditorDocView: DocumentView | undefined;
+    public static get StartLink() { return DocButtonState.Instance.StartLink; } // prettier-ignore
+    public static set StartLink(value) { runInAction(() => (DocButtonState.Instance.StartLink = value)); } // prettier-ignore
+    @observable public static StartLinkView: DocumentView | undefined = undefined;
+    @observable public static AnnotationId: string | undefined = undefined;
+    @observable public static AnnotationUri: string | undefined = undefined;
+
+    _prevProps: React.PropsWithChildren<DocumentLinksButtonProps>;
+    @observable _props: React.PropsWithChildren<DocumentLinksButtonProps>;
+    constructor(props: React.PropsWithChildren<DocumentLinksButtonProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
 
-    @action
     @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
-        if (this.props.InMenu && this.props.StartLink) {
+        if (this._props.InMenu && this._props.StartLink) {
             if (this._linkButton.current !== null) {
                 const linkDrag = UndoManager.StartBatch('Drag Link');
-                this.props.View &&
-                    DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
+                this._props.View &&
+                    DragManager.StartLinkDrag(this._linkButton.current, this._props.View, this._props.View.ComponentView?.getAnchor, e.pageX, e.pageY, {
                         dragComplete: dropEv => {
-                            if (this.props.View && dropEv.linkDocument) {
+                            if (this._props.View && dropEv.linkDocument) {
                                 // dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
                                 !dropEv.linkDocument.link_relationship && (Doc.GetProto(dropEv.linkDocument).link_relationship = 'hyperlink');
                             }
@@ -69,11 +94,11 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             this.onLinkButtonMoved,
             emptyFunction,
             action((e, doubleTap) => {
-                doubleTap && DocumentView.showBackLinks(this.props.View.Document);
+                doubleTap && DocumentView.showBackLinks(this._props.View.Document);
             }),
             undefined,
             undefined,
-            action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View))
+            action(() => (DocButtonState.Instance.LinkEditorDocView = this._props.View))
         );
     };
 
@@ -84,33 +109,32 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             this.onLinkButtonMoved,
             emptyFunction,
             action((e, doubleTap) => {
-                if (doubleTap && this.props.InMenu && this.props.StartLink) {
-                    //action(() => Doc.BrushDoc(this.props.View.Document));
-                    if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+                if (doubleTap && this._props.InMenu && this._props.StartLink) {
+                    //action(() => Doc.BrushDoc(this._props.View.Document));
+                    if (DocumentLinksButton.StartLink === this._props.View.Document) {
                         DocumentLinksButton.StartLink = undefined;
                         DocumentLinksButton.StartLinkView = undefined;
                     } else {
-                        DocumentLinksButton.StartLink = this.props.View.props.Document;
-                        DocumentLinksButton.StartLinkView = this.props.View;
+                        DocumentLinksButton.StartLink = this._props.View.Document;
+                        DocumentLinksButton.StartLinkView = this._props.View;
                     }
                 }
             })
         );
     };
 
-    @action
     @undoBatch
     onLinkClick = (e: React.MouseEvent): void => {
-        if (this.props.InMenu && this.props.StartLink) {
+        if (this._props.InMenu && this._props.StartLink) {
             DocumentLinksButton.AnnotationId = undefined;
             DocumentLinksButton.AnnotationUri = undefined;
-            if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+            if (DocumentLinksButton.StartLink === this._props.View.Document) {
                 DocumentLinksButton.StartLink = undefined;
                 DocumentLinksButton.StartLinkView = undefined;
             } else {
                 //if this LinkButton's Document is undefined
-                DocumentLinksButton.StartLink = this.props.View.props.Document;
-                DocumentLinksButton.StartLinkView = this.props.View;
+                DocumentLinksButton.StartLink = this._props.View.Document;
+                DocumentLinksButton.StartLinkView = this._props.View;
             }
         }
     };
@@ -121,7 +145,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             e,
             returnFalse,
             emptyFunction,
-            action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View))
+            action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this._props.View.Document, true, this._props.View))
         );
     };
 
@@ -133,7 +157,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 DocumentLinksButton.StartLinkView = undefined;
                 DocumentLinksButton.AnnotationId = undefined;
                 DocumentLinksButton.AnnotationUri = undefined;
-                //!this.props.StartLink
+                //!this._props.StartLink
             } else if (startLink !== endLink) {
                 endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
                 startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
@@ -190,8 +214,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @computed get filteredLinks() {
         const results = [] as Doc[];
-        const filters = this.props.View.props.childFilters();
-        Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
+        const filters = this._props.View._props.childFilters();
+        Array.from(new Set<Doc>(this._props.View.allLinks)).forEach(link => {
             if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) {
                 results.push(link);
             }
@@ -206,8 +230,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
      */
     @computed get linkButtonInner() {
         const btnDim = 30;
-        const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink;
-        const scaling = Math.min(1, this.props.scaling?.() || 1);
+        const isActive = DocumentLinksButton.StartLink === this._props.View.Document && this._props.StartLink;
+        const scaling = Math.min(1, this._props.scaling?.() || 1);
         const showLinkCount = (onHover?: boolean, offset?: boolean) => (
             <div
                 className="documentLinksButton-showCount"
@@ -221,16 +245,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 <span style={{ width: '100%', display: 'inline-block', textAlign: 'center' }}>{Array.from(this.filteredLinks).length}</span>
             </div>
         );
-        return this.props.ShowCount ? (
-            showLinkCount(this.props.OnHover, this.props.Bottom)
+        return this._props.ShowCount ? (
+            showLinkCount(this._props.OnHover, this._props.Bottom)
         ) : (
             <div className="documentLinksButton-menu">
-                {this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
+                {this._props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again
                     <div className={`documentLinksButton ${isActive ? `startLink` : ``}`} ref={this._linkButton} onPointerDown={isActive ? StopEvent : this.onLinkButtonDown} onClick={isActive ? this.clearLinks : this.onLinkClick}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                     </div>
                 ) : null}
-                {!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node
+                {!this._props.StartLink && DocumentLinksButton.StartLink !== this._props.View.Document ? ( //if the origin node is not this node
                     <div className={'documentLinksButton-endLink'} ref={this._linkButton} onPointerDown={DocumentLinksButton.StartLink && this.completeLink}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="link" />
                     </div>
@@ -240,21 +264,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     }
 
     render() {
-        if (this.props.hideCount?.()) return null;
-        const menuTitle = this.props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
+        if (this._props.hideCount?.()) return null;
+        const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
         const buttonTitle = 'Tap to view links; double tap to open link collection';
-        const title = this.props.ShowCount ? buttonTitle : menuTitle;
+        const title = this._props.ShowCount ? buttonTitle : menuTitle;
 
         //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
-        return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? null : (
+        return !Array.from(this.filteredLinks).length && !this._props.AlwaysOn ? null : (
             <div
                 className="documentLinksButton-wrapper"
                 style={{
-                    position: this.props.InMenu ? 'relative' : 'absolute',
+                    position: this._props.InMenu ? 'relative' : 'absolute',
                     top: 0,
                     pointerEvents: 'none',
                 }}>
-                {DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
+                {DocButtonState.Instance.LinkEditorDocView ? this.linkButtonInner : <Tooltip title={<div className="dash-tooltip">{title}</div>}>{this.linkButtonInner}</Tooltip>}
             </div>
         );
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f2a910023..b3acb08e2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,9 +1,9 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
-import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
+// import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -15,7 +15,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { DocOptions, Docs, DocUtils, FInfo } from '../../documents/Documents';
@@ -254,6 +254,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _titleRef = React.createRef<EditableView>();
     private _dropDisposer?: DragManager.DragDropDisposer;
 
+    @override _props: DocumentViewInternalProps;
+    _prevProps: DocumentViewInternalProps;
+
+    constructor(props: DocumentViewInternalProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        // untracked(() => (this._props = this._props));
+    }
+
     @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
     @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views.  defaults to 300 if not uset
     @observable _animateScalingTo = 0;
@@ -262,7 +275,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return this._animateScaleTime ?? 100;
     }
     public get displayName() {
-        return 'DocumentViewInternal(' + this.props.Document.title + ')';
+        return 'DocumentViewInternal(' + this._props.Document.title + ')';
     } // this makes mobx trace() statements more descriptive
 
     public get ContentDiv() {
@@ -272,43 +285,43 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return Doc.LayoutFieldKey(this.layoutDoc);
     }
     @computed get layout_showTitle() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowTitle) as Opt<string>;
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
     }
     @computed get NativeDimScaling() {
-        return this.props.NativeDimScaling?.() || 1;
+        return this._props.NativeDimScaling?.() || 1;
     }
     @computed get thumb() {
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
     }
     @computed get opacity() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Opacity);
     }
     @computed get boxShadow() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow);
     }
     @computed get borderRounding() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
     }
     @computed get widgetDecorations() {
         TraceMobx();
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Decorations);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
     }
     @computed get backgroundBoxColor() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
     }
     @computed get docContents() {
-        return this.props.styleProvider?.(this.Document, this.props, StyleProp.DocContents);
+        return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
     }
     @computed get headerMargin() {
-        return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
     }
     @computed get layout_showCaption() {
-        return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowCaption) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
     }
     @computed get titleHeight() {
-        return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0;
+        return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
     }
-    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined;
+    @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
     @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
         return this._pointerEvents;
     }
@@ -316,7 +329,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return StrCast(this.Document.layout_fieldKey, 'layout');
     }
     @computed get disableClickScriptFunc() {
-        const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
+        const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
         return (
             DocumentView.LongPress ||
@@ -325,49 +338,49 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         );
     }
     @computed get onClickHandler() {
-        return this.props.onClick?.() ?? this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+        return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
     }
     @computed get onDoubleClickHandler() {
-        return this.props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+        return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
     }
     @computed get onPointerDownHandler() {
-        return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+        return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
     }
     @computed get onPointerUpHandler() {
-        return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+        return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
     }
 
     componentWillUnmount() {
         this.cleanupHandlers(true);
     }
     @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
-    @action
+
     componentDidMount() {
-        this._mounted = true;
+        runInAction(() => (this._mounted = true));
         this.setupHandlers();
         this._disposers.contentActive = reaction(
             () => {
                 //  true  - if the document has been activated directly or indirectly (by having its children selected)
                 //  false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
                 // undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
-                return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none'
+                return this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none'
                     ? false
-                    : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
-                    ? true
-                    : undefined;
+                    : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
+                      ? true
+                      : undefined;
             },
             active => (this._isContentActive = active),
             { fireImmediately: true }
         );
         this._disposers.pointerevents = reaction(
-            () => this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents),
+            () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
             pointerevents => (this._pointerEvents = pointerevents),
             { fireImmediately: true }
         );
     }
     preDropFunc = (e: Event, de: DragManager.DropEvent) => {
         const dropAction = this.layoutDoc.dropAction as dropActionType;
-        if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) {
+        if (de.complete.docDragData && this.isContentActive() && !this._props.treeViewDoc) {
             dropAction && (de.complete.docDragData.dropAction = dropAction);
             e.stopPropagation();
         }
@@ -375,38 +388,38 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     setupHandlers() {
         this.cleanupHandlers(false);
         if (this._mainCont.current) {
-            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc);
+            this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
         }
     }
-    @action
+
     cleanupHandlers(unbrush: boolean) {
         this._dropDisposer?.();
-        unbrush && Doc.UnBrushDoc(this.props.Document);
+        unbrush && Doc.UnBrushDoc(this._props.Document);
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
             const views = SelectionManager.Views().filter(dv => dv.docView?._mainCont.current);
-            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this.props.DocumentView()];
+            const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
-            dragData.offset = this.props
+            const [left, top] = this._props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
+            dragData.offset = this._props
                 .ScreenToLocalTransform()
                 .scale(this.NativeDimScaling)
                 .transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
-            dragData.treeViewDoc = this.props.treeViewDoc;
-            dragData.removeDocument = this.props.removeDocument;
-            dragData.moveDocument = this.props.moveDocument;
-            dragData.draggedViews = [this.props.DocumentView()];
-            dragData.canEmbed = this.Document.dragAction ?? this.props.dragAction ? true : false;
+            dragData.treeViewDoc = this._props.treeViewDoc;
+            dragData.removeDocument = this._props.removeDocument;
+            dragData.moveDocument = this._props.moveDocument;
+            dragData.draggedViews = [this._props.DocumentView()];
+            dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.docView!._mainCont.current!),
                 dragData,
                 x,
                 y,
-                { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }
+                { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this._props.dontHideOnDrag) }
             ); // this needs to happen after the drop event is processed.
         }
     }
@@ -429,28 +442,28 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
 
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
-        if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
-        if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+        if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
+        if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
             let stopPropagate = true;
             let preventDefault = true;
-            !this.layoutDoc._keepZWhenDragged && this.props.bringToFront(this.Document);
+            !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
             if (this._doubleTap) {
-                const defaultDblclick = this.props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
+                const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
                 if (this.onDoubleClickHandler?.script) {
                     const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
                     // prettier-ignore
                     const func = () => this.onDoubleClickHandler.script.run( {
                             this: this.Document,
-                            scriptContext: this.props.scriptContext,
-                            documentView: this.props.DocumentView(),
+                            scriptContext: this._props.scriptContext,
+                            documentView: this._props.DocumentView(),
                             clientX, clientY,  altKey,  shiftKey,  ctrlKey,
                             value: undefined,
                         }, console.log  );
-                    UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
+                    UndoManager.RunInBatch(() => (func().result?.select === true ? this._props.select(false) : ''), 'on double click');
                 } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
                     UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
-                    Doc.UnBrushDoc(this.props.Document);
+                    Doc.UnBrushDoc(this._props.Document);
                 } else {
                     this._singleClickFunc?.();
                 }
@@ -467,13 +480,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
                         // instead of in the global lightbox
                         const oldFunc = DocumentViewInternal.addDocTabFunc;
-                        DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
+                        DocumentViewInternal.addDocTabFunc = this._props.addDocTab;
                         this.onClickHandler?.script.run(
                             {
                                 this: this.Document,
                                 _readOnly_: false,
-                                scriptContext: this.props.scriptContext,
-                                documentView: this.props.DocumentView(),
+                                scriptContext: this._props.scriptContext,
+                                documentView: this._props.DocumentView(),
                                 clientX,
                                 clientY,
                                 shiftKey,
@@ -482,14 +495,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             },
                             console.log
                         ).result?.select === true
-                            ? this.props.select(false)
+                            ? this._props.select(false)
                             : '';
                         DocumentViewInternal.addDocTabFunc = oldFunc;
                     };
                     clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.Document.title);
                 } else {
                     // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
-                    if ((this.layoutDoc.onDragStart || this.props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
+                    if ((this.layoutDoc.onDragStart || this._props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
                         stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                     }
                     preventDefault = false;
@@ -497,10 +510,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.Document, true) : 
+                    clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) : 
                                                       this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? 
-                                        this.props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
-                const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
+                                        this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
+                const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
                 if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
                     this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout);
                     this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300);
@@ -514,40 +527,39 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     });
 
-    @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
+        if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
         this._longPressSelector = setTimeout(() => {
             if (DocumentView.LongPress) {
                 if (this.Document.undoIgnoreFields) {
                     runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
                 } else {
-                    this.props.select(false);
+                    this._props.select(false);
                 }
             }
         }, 1000);
-        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView();
+        if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
 
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
-        if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.TemplateDataDocument && !(e.ctrlKey || e.button > 0))) {
+        if ((Doc.ActiveTool === InkTool.None || this._props.addDocTab === returnFalse) && !(this._props.TemplateDataDocument && !(e.ctrlKey || e.button > 0))) {
             // click events stop here if the document is active and no modes are overriding it
             // if this is part of a template, let the event go up to the template root unless right/ctrl clicking
             if (
                 // prettier-ignore
-                (this.props.isDocumentActive?.() || this.props.isContentActive?.()) &&
-                !this.props.onBrowseClick?.() &&
+                (this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
+                !this._props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
                 e.button === 0 &&
                 !Doc.IsInMyOverlay(this.layoutDoc)
             ) {
                 e.stopPropagation();
                 // don't preventDefault anymore.  Goldenlayout, PDF text selection and RTF text selection all need it to go though
-                //if (this.props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
+                //if (this._props.isSelected(true) && this.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
 
                 // listen to move events if document content isn't active or document is draggable
-                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this.props.dragWhenActive))) {
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) {
                     document.addEventListener('pointermove', this.onPointerMove);
                 }
             }
@@ -555,14 +567,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    @action
     onPointerMove = (e: PointerEvent): void => {
         if (e.buttons !== 1 || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) return;
 
         if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
             this.cleanupPointerEvents();
             this._longPressSelector && clearTimeout(this._longPressSelector);
-            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType));
+            this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
         }
     };
 
@@ -571,7 +582,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         document.removeEventListener('pointerup', this.onPointerUp);
     };
 
-    @action
     onPointerUp = (e: PointerEvent): void => {
         this.cleanupPointerEvents();
         this._longPressSelector && clearTimeout(this._longPressSelector);
@@ -586,7 +596,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @undoBatch
-    @action
     toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
         const hadOnClick = this.Document.onClick;
         this.noOnClick();
@@ -594,8 +603,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
     };
     @undoBatch
-    @action
-    followLinkOnClick = (): void => {
+    followLinkOnClick = () => {
         this.Document.ignoreClick = false;
         this.Document.onClick = FollowLinkScript();
         this.Document.followLinkToggle = false;
@@ -603,12 +611,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this.Document.followLinkLocation = undefined;
     };
     @undoBatch
-    noOnClick = (): void => {
+    noOnClick = () => {
         this.Document.ignoreClick = false;
         this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
     };
 
-    @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
+    @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
     @undoBatch setToggleDetail = () =>
         (this.Document.onClick = ScriptField.MakeScript(
             `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
@@ -618,10 +626,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         ));
 
     @undoBatch
-    @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
-        if (this.props.Document === Doc.ActiveDashboard) {
+        if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
+        if (this._props.Document === Doc.ActiveDashboard) {
             e.stopPropagation();
             e.preventDefault();
             alert(
@@ -643,7 +650,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
                         de.complete.linkDocument.layout_isSvg = true;
-                        this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+                        this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
                     }
                 }
                 e.stopPropagation();
@@ -654,18 +661,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @undoBatch
-    @action
     makeIntoPortal = () => {
-        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this.props.Document && d.link_relationship === 'portal to:portal from');
+        const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
             DocUtils.MakeLink(
-                this.props.Document,
+                this._props.Document,
                 Docs.Create.FreeformDocument([], {
                     _width: NumCast(this.layoutDoc._width) + 10,
                     _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
                     _isLightbox: true,
                     _layout_fitWidth: true,
-                    title: StrCast(this.props.Document.title) + ' [Portal]',
+                    title: StrCast(this._props.Document.title) + ' [Portal]',
                 }),
                 { link_relationship: 'portal to:portal from' }
             );
@@ -683,7 +689,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const batch = UndoManager.StartBatch('importing');
                 Doc.importDocument(input.files[0]).then(doc => {
                     if (doc instanceof Doc) {
-                        this.props.addDocTab(doc, OpenWhere.addRight);
+                        this._props.addDocTab(doc, OpenWhere.addRight);
                         batch.end();
                     }
                 });
@@ -692,12 +698,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         input.click();
     };
 
-    @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
         if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
-            //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+            //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
         }
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (e) {
@@ -719,7 +724,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         if (e && !(e.nativeEvent as any).dash) {
             const onDisplay = () => {
-                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected() && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
+                if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && SelectionManager.SelectView(this._props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up.  by delaying the selection until hopefully after the pointer up, the context menu will appear.
                 setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
             };
             if (navigator.userAgent.includes('Macintosh')) {
@@ -730,33 +735,33 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             return;
         }
 
-        const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
+        const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
-            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this.props.scriptContext }), icon: 'sticky-note' })
+            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
         );
-        this.props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this.props.scriptContext }), icon: item.icon as IconProp }));
+        this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
 
-        if (!this.props.Document.isFolder) {
-            const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null);
+        if (!this._props.Document.isFolder) {
+            const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
             const appearance = cm.findByDescription('Appearance...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
 
-            if (this.props.renderDepth === 0) {
+            if (this._props.renderDepth === 0) {
                 appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
             }
-            appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.Document, {}), icon: 'eye' });
-            !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
+            appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' });
+            !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
 
             if (!Doc.IsSystem(this.Document) && this.Document.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._type_collection as any)) {
                 const existingOnClick = cm.findByDescription('OnClick...');
                 const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
 
-                if (this.props.bringToFront !== emptyFunction) {
+                if (this._props.bringToFront !== emptyFunction) {
                     const zorders = cm.findByDescription('ZOrder...');
                     const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
-                    zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
-                    zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
+                    zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views().forEach(dv => dv._props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
+                    zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv._props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
                     zorderItems.push({
                         description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
                         event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
@@ -768,14 +773,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
                 !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
 
-                if (!this.props.treeViewDoc) {
+                if (!this._props.treeViewDoc) {
                     if (!this.Document.annotationOn) {
                         const options = cm.findByDescription('Options...');
                         const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
                         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
 
                         onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
-                        !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+                        !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this._props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
                         !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
                     } else if (LinkManager.Links(this.Document).length) {
                         onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
@@ -797,15 +802,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const moreItems = more && 'subitems' in more ? more.subitems : [];
             if (!Doc.IsSystem(this.Document)) {
                 if (!Doc.noviceMode) {
-                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.TemplateDataDocument), icon: 'concierge-bell' });
+                    moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
 
-                    if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
-                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: 'caret-square-right' });
-                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: 'caret-square-right' });
+                    if (Cast(Doc.GetProto(this._props.Document).data, listSpec(Doc))) {
+                        moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this._props.Document }).then(console.log), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this._props.Document), icon: 'caret-square-right' });
+                        moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this._props.Document), icon: 'caret-square-right' });
                     }
-                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' });
+                    moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
                 }
             }
 
@@ -813,25 +818,25 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         const constantItems: ContextMenuProps[] = [];
         if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
-            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
-            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
-            if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) {
+            constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
+            (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
+            if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
                 // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
         }
-        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+        constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
         cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
 
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
-        !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
+        !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this._props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
         !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.Document), icon: 'hand-point-right' });
         !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.dataDoc), icon: 'hand-point-right' });
 
         let documentationDescription: string | undefined = undefined;
         let documentationLink: string | undefined = undefined;
-        switch (this.props.Document.type) {
+        switch (this._props.Document.type) {
             case DocumentType.COL:
                 documentationDescription = 'See collection documentation';
                 documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -866,7 +871,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 break;
         }
         // Add link to help documentation
-        if (!this.props.treeViewDoc && documentationDescription && documentationLink) {
+        if (!this._props.treeViewDoc && documentationDescription && documentationLink) {
             helpItems.push({
                 description: documentationDescription,
                 event: () => window.open(documentationLink, '_blank'),
@@ -881,26 +886,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @computed get _rootSelected() {
-        return this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.());
+        return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
     }
     rootSelected = () => this._rootSelected;
-    panelHeight = () => this.props.PanelHeight() - this.headerMargin;
-    screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+    panelHeight = () => this._props.PanelHeight() - this.headerMargin;
+    screenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
-    setHeight = (height: number) => !this.props.suppressSetHeight && (this.layoutDoc._height = height);
+    setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    @observable _isContentActive: boolean | undefined;
+    @observable _isContentActive: boolean | undefined = undefined;
 
     isContentActive = (): boolean | undefined => this._isContentActive;
-    childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
+    childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
 
     /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
     @computed get _contentPointerEvents() {
         TraceMobx();
-        return this.props.contentPointerEvents ??
+        return this._props.contentPointerEvents ??
             ((!this.disableClickScriptFunc && //
                 this.onClickHandler &&
-                !this.props.onBrowseClick?.() &&
+                !this._props.onBrowseClick?.() &&
                 this.isContentActive() !== true) ||
                 this.isContentActive() === false)
             ? 'none'
@@ -909,8 +914,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     contentPointerEvents = () => this._contentPointerEvents;
     @computed get contents() {
         TraceMobx();
-        const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString;
-        const noBackground = this.Document.isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+        const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+        const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return (
             <div
                 className="documentView-contentsView"
@@ -920,10 +925,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 }}>
                 <DocumentContentsView
                     key={1}
-                    {...this.props}
+                    {...this._props}
                     fieldKey=""
                     pointerEvents={this.contentPointerEvents}
-                    docViewPath={this.props.viewPath}
+                    docViewPath={this._props.viewPath}
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
                     PanelHeight={this.panelHeight}
@@ -940,8 +945,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         );
     }
 
-    anchorPanelWidth = () => this.props.PanelWidth() || 1;
-    anchorPanelHeight = () => this.props.PanelHeight() || 1;
+    anchorPanelWidth = () => this._props.PanelWidth() || 1;
+    anchorPanelHeight = () => this._props.PanelHeight() || 1;
     anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         // prettier-ignore
         switch (property.split(':')[0]) {
@@ -949,11 +954,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             case StyleProp.PointerEvents: return 'none';
             case StyleProp.Highlighting:  return undefined;
             case StyleProp.Opacity:       {
-                const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+                const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
                 return filtered.some(link => link._link_displayArrow) ? 0 : undefined;
             }
         }
-        return this.props.styleProvider?.(doc, props, property);
+        return this._props.styleProvider?.(doc, props, property);
     };
     // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
     // anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document).  e.g.,
@@ -979,15 +984,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get allLinkEndpoints() {
         // the small blue dots that mark the endpoints of links
         TraceMobx();
-        if (this._componentView instanceof KeyValueBox || this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
-        const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
+        if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
+        const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
         return filtered.map(link => (
             <div className="documentView-anchorCont" key={link[Id]}>
                 <DocumentView
-                    {...this.props}
+                    {...this._props}
                     isContentActive={returnFalse}
                     Document={link}
-                    docViewPath={this.props.viewPath}
+                    docViewPath={this._props.viewPath}
                     PanelWidth={this.anchorPanelWidth}
                     PanelHeight={this.anchorPanelHeight}
                     dontRegisterView={false}
@@ -1077,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     @observable _changingTitleField = false;
     @observable _dropDownInnerWidth = 0;
     fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1121,12 +1126,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
                     background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
                     color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
                 }}>
                 <FormattedTextBox
-                    {...this.props}
+                    {...this._props}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={this.layout_showCaption}
@@ -1134,7 +1139,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     dontRegisterView={true}
                     noSidebar={true}
                     dontScale={true}
-                    renderDepth={this.props.renderDepth}
+                    renderDepth={this._props.renderDepth}
                     isContentActive={this.isContentActive}
                 />
             </div>
@@ -1160,7 +1165,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     width: 100 - sidebarWidthPercent + '%',
                     color: background === 'transparent' ? SettingsManager.userColor : lightOrDark(background),
                     background,
-                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined,
+                    pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
                 }}>
                 {!dropdownWidth
                     ? null
@@ -1171,7 +1176,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                           action((field: string | number) => {
                               if (this.layoutDoc.layout_showTitle) {
                                   this.layoutDoc._layout_showTitle = field;
-                              } else if (!this.props.layout_showTitle) {
+                              } else if (!this._props.layout_showTitle) {
                                   Doc.UserDoc().layout_showTitle = field;
                               }
                               this._changingTitleField = false;
@@ -1199,7 +1204,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             if (input?.startsWith('#')) {
                                 if (this.layoutDoc.layout_showTitle) {
                                     this.layoutDoc._layout_showTitle = input?.substring(1);
-                                } else if (!this.props.layout_showTitle) {
+                                } else if (!this._props.layout_showTitle) {
                                     Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
                                 }
                             } else if (showTitle && !showTitle.includes('Date') && showTitle !== 'author') {
@@ -1211,7 +1216,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 </div>
             </div>
         );
-        return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+        return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
             this.contents
         ) : (
             <div className="documentView-styleWrapper">
@@ -1267,31 +1272,31 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
             default: 
             case PresEffect.None:       return renderDoc;
-            case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
-            case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
-            case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
-            case PresEffect.Rotate:     return <Rotate {...effectProps}>{renderDoc}</Rotate>;
-            case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
-            case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
-            case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
+            // case PresEffect.Zoom:       return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+            // case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>;
+            // case PresEffect.Flip:       return <Flip {...effectProps}>{renderDoc}</Flip>;
+            // case PresEffect.Rotate:     return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+            // case PresEffect.Bounce:     return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+            // case PresEffect.Roll:       return <Roll {...effectProps}>{renderDoc}</Roll>;
+            // case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>;
         }
     }
     @computed get highlighting() {
-        return this.props.styleProvider?.(this.Document, this.props, StyleProp.Highlighting);
+        return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
     }
     @computed get borderPath() {
-        return this.props.styleProvider?.(this.Document, this.props, StyleProp.BorderPath);
+        return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
     }
     render() {
         TraceMobx();
         const highlighting = this.highlighting;
         const borderPath = this.borderPath;
         const boxShadow =
-            this.props.treeViewDoc || !highlighting
+            this._props.treeViewDoc || !highlighting
                 ? this.boxShadow
                 : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
-                ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
-                : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+                  ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+                  : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
         const renderDoc = this.renderDoc({
             borderRadius: this.borderRounding,
             outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1326,6 +1331,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 @observer
 export class DocumentView extends React.Component<DocumentViewProps> {
     public static ROOT_DIV = 'documentView-effectsWrapper';
+
+    @observable _props: DocumentViewProps;
+    _prevProps: DocumentViewProps;
+    constructor(props: any) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable _selected = false;
     public get SELECTED() {
         return this._selected;
@@ -1340,14 +1357,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed public static get exploreMode() {
         return () => (DocumentView.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
     }
-    @observable public docView: DocumentViewInternal | undefined | null;
+    @observable public docView: DocumentViewInternal | undefined | null = undefined;
     @observable public textHtmlOverlay: Opt<string>;
     @observable public textHtmlOverlayTime: Opt<number>;
     @observable private _isHovering = false;
 
     public htmlOverlayEffect: Opt<Doc>;
     public get displayName() {
-        return 'DocumentView(' + this.props.Document?.title + ')';
+        return 'DocumentView(' + this._props.Document?.title + ')';
     } // this makes mobx trace() statements more descriptive
     public ContentRef = React.createRef<HTMLDivElement>();
     public ViewTimer: NodeJS.Timeout | undefined; // timer for res
@@ -1405,10 +1422,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     get Document() {
-        return this.props.Document;
+        return this._props.Document;
     }
     get topMost() {
-        return this.props.renderDepth === 0;
+        return this._props.renderDepth === 0;
     }
     get dataDoc() {
         return this.docView?.dataDoc ?? this.Document;
@@ -1426,32 +1443,32 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     @computed get layout_fitWidth() {
-        return this.props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
+        return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
     }
     @computed get anchorViewDoc() {
-        return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
+        return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
     @computed get hideLinkButton() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
     }
-    hideLinkCount = () => this.props.renderDepth === -1 || (this.SELECTED && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
+    hideLinkCount = () => this._props.renderDepth === -1 || (this.SELECTED && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
     @computed get linkCountView() {
         return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
     @computed get docViewPath(): DocumentView[] {
-        return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
+        return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
     }
     @computed get layoutDoc() {
-        return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
+        return Doc.Layout(this.Document, this._props.LayoutTemplate?.());
     }
     @computed get nativeWidth() {
-        return this.props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
     }
     @computed get nativeHeight() {
-        return this.props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
+        return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
     }
     @computed get shouldNotScale() {
-        return (this.layout_fitWidth && !this.nativeWidth) || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
+        return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
     }
     @computed get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1462,57 +1479,57 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get nativeScaling() {
         if (this.shouldNotScale) return 1;
         const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
-        if (this.layout_fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
-            return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
+        if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+            return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
         }
-        return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+        return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
     @computed get panelWidth() {
-        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth();
+        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
     }
     @computed get panelHeight() {
         if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
-            return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
+            return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
-        return this.props.PanelHeight();
+        return this._props.PanelHeight();
     }
     @computed get Xshift() {
-        return this.effectiveNativeWidth ? Math.max(0, (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
+        return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
     }
     @computed get Yshift() {
         return this.effectiveNativeWidth &&
             this.effectiveNativeHeight &&
             Math.abs(this.Xshift) < 0.001 &&
-            (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight()))
-            ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
+            (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this._props.PanelHeight()))
+            ? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
             : 0;
     }
     @computed get centeringX() {
-        return this.props.dontCenter?.includes('x') ? 0 : this.Xshift;
+        return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
     }
     @computed get centeringY() {
-        return this.props.dontCenter?.includes('y') ? 0 : this.Yshift;
+        return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
     }
 
     @computed get CollectionFreeFormView() {
         return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
     }
     @computed get CollectionFreeFormDocumentView() {
-        return this.props.CollectionFreeFormDocumentView?.();
+        return this._props.CollectionFreeFormDocumentView?.();
     }
 
-    public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
+    public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
     public getBounds = () => {
-        if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+        if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
             return undefined;
         }
         if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
-        const xf = this.docView.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+        const xf = this.docView._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
-        if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+        if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
             const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
@@ -1535,18 +1552,17 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
             this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
             this.Document.deiconifyLayout = undefined;
-            this.props.bringToFront(this.Document);
+            this._props.bringToFront(this.Document);
         }
     }
     @undoBatch
-    @action
     setCustomView = (custom: boolean, layout: string): void => {
-        Doc.setNativeView(this.props.Document);
-        custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+        Doc.setNativeView(this._props.Document);
+        custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
     };
-    @action
+
     switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
-        this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+        runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
         setTimeout(
             action(() => {
                 if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1568,7 +1584,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     };
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
-    scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
+    scaleToScreenSpace = () => (1 / (this._props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = () => this.SELECTED;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1592,14 +1608,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     NativeDimScaling = () => this.nativeScaling;
     selfView = () => this;
     screenToLocalTransform = () =>
-        this.props
+        this._props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
             .scale(1 / this.nativeScaling);
 
-    @action
     componentDidMount() {
-        this.Document[DocViews].add(this);
+        runInAction(() => this.Document[DocViews].add(this));
         this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
         this._disposers.height = reaction(
             // increase max auto height if document has been resized to be greater than current max
@@ -1609,13 +1624,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                 if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
             })
         );
-        !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
     }
-    @action
+
     componentWillUnmount() {
         this.Document[DocViews].delete(this);
         Object.values(this._disposers).forEach(disposer => disposer?.());
-        !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+        !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
     // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
     // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
@@ -1652,23 +1667,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
-        const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+        const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
+        const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
 
         return (
             <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
-                {!this.props.Document || !this.props.PanelWidth() ? null : (
+                {!this._props.Document || !this._props.PanelWidth() ? null : (
                     <div
                         className="contentFittingDocumentView-previewDoc"
                         ref={this.ContentRef}
                         style={{
-                            transition: 'inherit', // this.props.dataTransition,
+                            transition: 'inherit', // this._props.dataTransition,
                             transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                            width: xshift ?? `${this.props.PanelWidth() - this.Xshift * 2}px`,
-                            height: this.props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this.props.PanelWidth()}px`),
+                            width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
+                            height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
                         }}>
                         <DocumentViewInternal
-                            {...this.props}
+                            {...this._props}
                             DocumentView={this.selfView}
                             viewPath={this.docViewPathFunc}
                             PanelWidth={this.PanelWidth}
@@ -1680,7 +1695,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             select={this.select}
                             layout_fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToLocalTransform}
-                            focus={this.props.focus || emptyFunction}
+                            focus={this._props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
                         />
                         {this.htmlOverlay}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 23413b9d1..02ed56333 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,5 +1,5 @@
 import EquationEditor from './formattedText/EquationEditor';
-import { action, reaction } from 'mobx';
+import { action, makeObservable, override, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Id } from '../../../fields/FieldSymbols';
@@ -11,6 +11,7 @@ import { ViewBoxBaseComponent } from '../DocComponent';
 import { LightboxView } from '../LightboxView';
 import './EquationBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
+import { copyProps } from '../../../Utils';
 
 @observer
 export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -19,10 +20,23 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     public static SelectOnLoad: string = '';
     _ref: React.RefObject<EquationEditor> = React.createRef();
+
+    _prevProps: React.PropsWithChildren<FieldViewProps>;
+    @override _props: React.PropsWithChildren<FieldViewProps>;
+    constructor(props: React.PropsWithChildren<FieldViewProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentDidMount() {
-        this.props.setContentView?.(this);
-        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
-            this.props.select(false);
+        this._props.setContentView?.(this);
+        if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+            this._props.select(false);
 
             this._ref.current!.mathField.focus();
             this.dataDoc.text === 'x' && this._ref.current!.mathField.select();
@@ -38,7 +52,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
             //{ fireImmediately: true }
         );
         reaction(
-            () => this.props.isSelected(),
+            () => this._props.isSelected(),
             selected => {
                 if (this._ref.current) {
                     if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true);
@@ -62,7 +76,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 y: NumCast(this.layoutDoc.y) + _height + 10,
             });
             EquationBox.SelectOnLoad = nextEq[Id];
-            this.props.addDocument?.(nextEq);
+            this._props.addDocument?.(nextEq);
             e.stopPropagation();
         }
         if (e.key === 'Tab') {
@@ -73,10 +87,10 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 _height: 300,
                 backgroundColor: 'white',
             });
-            this.props.addDocument?.(graph);
+            this._props.addDocument?.(graph);
             e.stopPropagation();
         }
-        if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.Document);
+        if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document);
     };
     @undoBatch
     onChange = (str: string) => (this.dataDoc.text = str);
@@ -100,7 +114,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
     render() {
         TraceMobx();
-        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+        const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         return (
             <div
                 ref={r => this.updateSize()}
@@ -110,7 +124,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     transform: `scale(${scale})`,
                     width: 'fit-content', // `${100 / scale}%`,
                     height: `${100 / scale}%`,
-                    pointerEvents: !this.props.isSelected() ? 'none' : undefined,
+                    pointerEvents: !this._props.isSelected() ? 'none' : undefined,
                     fontSize: StrCast(this.layoutDoc._text_fontSize),
                 }}
                 onKeyDown={e => e.stopPropagation()}>
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8f6550663..22339907f 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,13 +1,13 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { computed, makeObservable, observable, override } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -45,6 +45,16 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
+    _prevProps: React.PropsWithChildren<ButtonProps>;
+    @override _props: React.PropsWithChildren<ButtonProps>;
+    constructor(props: React.PropsWithChildren<ButtonProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
     //
     // This controls whether fontIconButtons will display labels under their icons or not
     //
@@ -57,7 +67,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     @observable noTooltip = false;
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
-        dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
+        dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight);
     };
     dragAsTemplate = (): void => {
         this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
@@ -131,7 +141,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
                 break;
         }
         const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined });
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         // Script for checking the outcome of the toggle
         const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
 
@@ -246,7 +256,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
      * Color button
      */
     @computed get colorButton() {
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
         const tooltip: string = StrCast(this.Document.toolTip);
 
@@ -279,7 +289,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const script = ScriptCast(this.Document.onClick);
         const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const items = DocListCast(this.dataDoc.data);
         return (
             <MultiToggle
@@ -310,8 +320,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
         const script = ScriptCast(this.Document.onClick);
         const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+        const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
 
         return (
             <Toggle
@@ -333,8 +343,8 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
      * Default
      */
     @computed get defaultButton() {
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+        const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
         const tooltip: string = StrCast(this.Document.toolTip);
 
         return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />;
@@ -361,7 +371,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     }
 
     render() {
-        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
         const tooltip = StrCast(this.Document.toolTip);
         const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false });
         const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 5ed5fa8fd..a04c27e01 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -1,5 +1,5 @@
 import functionPlot from 'function-plot';
-import { action, computed, reaction } from 'mobx';
+import { action, computed, makeObservable, override, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
@@ -9,19 +9,14 @@ import { List } from '../../../fields/List';
 import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
 import { Cast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
+import { copyProps } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
-import { DocFocusOptions, DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
-const EquationSchema = createSchema({});
-
-type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>;
-const EquationDocument = makeInterface(EquationSchema, documentSchema);
-
 @observer
 export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
@@ -31,12 +26,22 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     _plot: any;
     _plotId = '';
     _plotEle: any;
-    constructor(props: any) {
+
+    _prevProps: React.PropsWithChildren<FieldViewProps>;
+    @override _props: React.PropsWithChildren<FieldViewProps>;
+    constructor(props: React.PropsWithChildren<FieldViewProps>) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
     }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         reaction(
             () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
             () => this.createGraph()
@@ -52,8 +57,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     };
     createGraph = (ele?: HTMLDivElement) => {
         this._plotEle = ele || this._plotEle;
-        const width = this.props.PanelWidth();
-        const height = this.props.PanelHeight();
+        const width = this._props.PanelWidth();
+        const height = this._props.PanelHeight();
         const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)'));
         try {
             this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]);
@@ -77,7 +82,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     @undoBatch
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData?.droppedDocuments.length) {
-            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true);
+            const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true);
             !added && e.preventDefault();
             e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place
             return added;
@@ -102,14 +107,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
             <div
                 ref={this.createDropTarget}
                 style={{
-                    pointerEvents: !this.props.isContentActive() ? 'all' : undefined,
-                    width: this.props.PanelWidth(),
-                    height: this.props.PanelHeight(),
+                    pointerEvents: !this._props.isContentActive() ? 'all' : undefined,
+                    width: this._props.PanelWidth(),
+                    height: this._props.PanelHeight(),
                 }}>
                 {this.theGraph}
                 <div
                     style={{
-                        display: this.props.isSelected() ? 'none' : undefined,
+                        display: this._props.isSelected() ? 'none' : undefined,
                         position: 'absolute',
                         width: '100%',
                         height: '100%',
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dbfeec1c3..091c1a32f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,25 +1,20 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
-import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
-import { createSchema } from '../../../fields/Schema';
-import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
-import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
-import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
+import { copyProps, DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
-import { Networking } from '../../Network';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
@@ -31,22 +26,9 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
 import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FaceRectangles } from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
-import * as React from 'react';
-
-export const pageSchema = createSchema({
-    googlePhotosUrl: 'string',
-    googlePhotosTags: 'string',
-});
-const uploadIcons = {
-    idle: 'downarrow.png',
-    loading: 'loading.gif',
-    success: 'greencheck.png',
-    failure: 'redx.png',
-};
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -54,10 +36,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
 
-    @observable public static imageRootDoc: Doc | undefined;
+    @observable public static imageRootDoc: Doc | undefined = undefined;
     @observable public static imageEditorOpen: boolean = false;
     @observable public static imageEditorSource: string = '';
-    @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+    @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined = undefined;
     @action public static setImageEditorOpen(open: boolean) {
         ImageBox.imageEditorOpen = open;
     }
@@ -72,16 +54,23 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _overlayIconRef = React.createRef<HTMLDivElement>();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
-    @observable _uploadIcon = uploadIcons.idle;
 
-    constructor(props: any) {
+    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
         super(props);
-        this.props.setContentView?.(this);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._props.setContentView?.(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer?.();
-        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document));
+        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -107,9 +96,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     componentDidMount() {
         this._disposers.sizer = reaction(
             () => ({
-                forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes,
-                scrSize: (this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
-                selected: this.props.isSelected(),
+                forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
+                scrSize: (this._props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
+                selected: this._props.isSelected(),
             }),
             ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
             { fireImmediately: true, delay: 1000 }
@@ -141,7 +130,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @undoBatch
-    @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.docDragData) {
             let added: boolean | undefined = undefined;
@@ -177,8 +165,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
-        const nscale = NumCast(this.props.PanelWidth()) / scaling;
+        const scaling = (this._props.DocumentView?.()._props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+        const nscale = NumCast(this._props.PanelWidth()) / scaling;
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
         this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
@@ -217,8 +205,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.title = 'crop: ' + this.Document.title;
         cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
         cropping.y = NumCast(this.Document.y);
-        cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
-        cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+        cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+        cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
         cropping.onClick = undefined;
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
@@ -242,10 +230,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
             cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
             cropping.y = NumCast(this.Document.y);
-            this.props.addDocTab(cropping, OpenWhere.inParent);
+            this._props.addDocTab(cropping, OpenWhere.inParent);
         }
         DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
-        this.props.bringToFront(cropping);
+        this._props.bringToFront(cropping);
         return cropping;
     };
 
@@ -262,55 +250,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 event: action(() => {
                     ImageBox.setImageEditorOpen(true);
                     ImageBox.setImageEditorSource(this.choosePath(field.url));
-                    ImageBox.addDoc = this.props.addDocument;
+                    ImageBox.addDoc = this._props.addDocument;
                     ImageBox.imageRootDoc = this.Document;
                 }),
                 icon: 'pencil-alt',
             });
-            if (!Doc.noviceMode) {
-                funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
-
-                const existingAnalyze = ContextMenu.Instance?.findByDescription('Analyzers...');
-                const modes: ContextMenuProps[] = existingAnalyze && 'subitems' in existingAnalyze ? existingAnalyze.subitems : [];
-                modes.push({ description: 'Generate Tags', event: this.generateMetadata, icon: 'tag' });
-                modes.push({ description: 'Find Faces', event: this.extractFaces, icon: 'camera' });
-                //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
-                !existingAnalyze && ContextMenu.Instance?.addItem({ description: 'Analyzers...', subitems: modes, icon: 'hand-point-right' });
-            }
-
             ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
         }
     };
 
-    extractFaces = () => {
-        const converter = (results: any) => {
-            return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
-        };
-        this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-faces'], this.url, Service.Face, converter);
-    };
-
-    generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
-        const converter = (results: any) => {
-            const tagDoc = new Doc();
-            const tagsList = new List();
-            results.tags.map((tag: Tag) => {
-                tagsList.push(tag.name);
-                const sanitized = tag.name.replace(' ', '_');
-                tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
-            });
-            this.dataDoc[this.fieldKey + '-generatedTags'] = tagsList;
-            tagDoc.title = 'Generated Tags Doc';
-            tagDoc.confidence = threshold;
-            return tagDoc;
-        };
-        this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-generatedTagsDoc'], this.url, Service.ComputerVision, converter);
-    };
-
-    @computed private get url() {
-        const data = Cast(this.dataDoc[this.fieldKey], ImageField);
-        return data ? data.url?.href : undefined;
-    }
-
     choosePath(url: URL) {
         if (!url?.href) return '';
         const lower = url.href.toLowerCase();
@@ -321,60 +269,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         const ext = extname(url.href);
         return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
     }
-
-    considerGooglePhotosLink = () => {
-        const remoteUrl = StrCast(this.dataDoc.googlePhotosUrl); // bcz: StrCast or URLCast???
-        return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />;
-    };
-
-    considerGooglePhotosTags = () => {
-        const tags = this.dataDoc.googlePhotosTags;
-        return !tags ? null : <img id={'google-tags'} src={'/assets/google_tags.png'} />;
-    };
-
-    getScrollHeight = () => (this.props.layout_fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
-
-    @computed
-    private get considerDownloadIcon() {
-        const data = this.dataDoc[this.fieldKey];
-        if (!(data instanceof ImageField)) {
-            return null;
-        }
-        const primary = data.url?.href;
-        if (primary.includes(window.location.origin)) {
-            return null;
-        }
-        return (
-            <img
-                id={'upload-icon'}
-                draggable={false}
-                style={{ transformOrigin: 'bottom right' }}
-                src={`/assets/${this._uploadIcon}`}
-                onClick={async () => {
-                    const { dataDoc } = this;
-                    const { success, failure, idle, loading } = uploadIcons;
-                    runInAction(() => (this._uploadIcon = loading));
-                    const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [primary] });
-                    dataDoc[this.props.fieldKey + '-originalUrl'] = primary;
-                    let succeeded = true;
-                    let data: ImageField | undefined;
-                    try {
-                        data = new ImageField(accessPaths.agnostic.client);
-                    } catch {
-                        succeeded = false;
-                    }
-                    runInAction(() => (this._uploadIcon = succeeded ? success : failure));
-                    setTimeout(
-                        action(() => {
-                            this._uploadIcon = idle;
-                            data && (dataDoc[this.fieldKey] = data);
-                        }),
-                        2000
-                    );
-                }}
-            />
-        );
-    }
+    getScrollHeight = () => (this._props.layout_fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
 
     @computed get nativeSize() {
         TraceMobx();
@@ -407,7 +302,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     ref={this._overlayIconRef}
                     onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))}
                     style={{
-                        display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+                        display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
                         width: 'min(10%, 25px)',
                         height: 'min(10%, 25px)',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -436,7 +331,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @computed get content() {
         TraceMobx();
 
-        const backColor = DashColor(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor));
+        const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
         const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1;
         const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
         const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
@@ -466,9 +361,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         </div>
                     )}
                 </div>
-                {!Doc.noviceMode && this.considerDownloadIcon}
-                {this.considerGooglePhotosLink()}
-                <FaceRectangles document={this.dataDoc} color={'#0000FF'} backgroundColor={'#0000FF'} />
                 {this.overlayImageIcon}
             </div>
         );
@@ -479,11 +371,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
         TraceMobx();
-        return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
+        return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
     }
-    screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.props.ScreenToLocalTransform().Scale);
+    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this._props.ScreenToLocalTransform().Scale);
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -502,7 +394,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     finishMarquee = () => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this._marqueeref.current?.onTerminateSelection();
-        this.props.select(false);
+        this._props.select(false);
     };
     focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
 
@@ -510,8 +402,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     savedAnnotations = () => this._savedAnnotations;
     render() {
         TraceMobx();
-        const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
-        const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.NativeDimScaling?.() || 1)}px` : borderRad;
+        const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
+        const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
         return (
             <div
                 className="imageBox"
@@ -527,25 +419,25 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     }
                 })}
                 style={{
-                    width: this.props.PanelWidth() ? undefined : `100%`,
-                    height: this.props.PanelWidth() ? undefined : `100%`,
+                    width: this._props.PanelWidth() ? undefined : `100%`,
+                    height: this._props.PanelWidth() ? undefined : `100%`,
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
-                    overflow: this.layoutDoc.layout_fitWidth || this.props.layout_fitWidth?.(this.Document) ? 'auto' : undefined,
+                    overflow: this.layoutDoc.layout_fitWidth || this._props.layout_fitWidth?.(this.Document) ? 'auto' : undefined,
                 }}>
                 <CollectionFreeFormView
                     ref={this._ffref}
-                    {...this.props}
+                    {...this._props}
                     setContentView={emptyFunction}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
-                    renderDepth={this.props.renderDepth + 1}
+                    renderDepth={this._props.renderDepth + 1}
                     fieldKey={this.annotationKey}
-                    styleProvider={this.props.styleProvider}
+                    styleProvider={this._props.styleProvider}
                     isAnnotationOverlay={true}
                     annotationLayerHostsContent={true}
-                    PanelWidth={this.props.PanelWidth}
-                    PanelHeight={this.props.PanelHeight}
+                    PanelWidth={this._props.PanelWidth}
+                    PanelHeight={this._props.PanelHeight}
                     ScreenToLocalTransform={this.screenToLocalTransform}
                     select={emptyFunction}
                     focus={this.focus}
@@ -566,8 +458,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         scrollTop={0}
                         annotationLayerScrollTop={0}
                         scaling={returnOne}
-                        annotationLayerScaling={this.props.NativeDimScaling}
-                        docView={this.props.DocumentView!}
+                        annotationLayerScaling={this._props.NativeDimScaling}
+                        docView={this._props.DocumentView!}
                         addDocument={this.addDocument}
                         finishMarquee={this.finishMarquee}
                         savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index d2325a807..9aab53daf 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
@@ -6,7 +6,7 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { DocCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { returnAll, returnAlways, returnTrue } from '../../../Utils';
+import { copyProps, returnAll, returnAlways, returnTrue } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
 import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
@@ -38,8 +38,20 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     private _keyInput = React.createRef<HTMLInputElement>();
     private _valInput = React.createRef<HTMLInputElement>();
 
+    _prevProps: FieldViewProps;
+    @observable _props: FieldViewProps;
+    constructor(props: FieldViewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
     }
     isKeyValueBox = returnTrue;
     able = returnAlways;
@@ -50,7 +62,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     @observable _splitPercentage = 50;
 
     get fieldDocToLayout() {
-        return this.props.fieldKey ? DocCast(this.props.Document[this.props.fieldKey], DocCast(this.props.Document)) : this.props.Document;
+        return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document;
     }
 
     @action
@@ -109,7 +121,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && this.props.isSelected()) {
+        if (e.buttons === 1 && this._props.isSelected()) {
             e.stopPropagation();
         }
     };
@@ -155,8 +167,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             rows.push(
                 <KeyValuePair
                     doc={realDoc}
-                    addDocTab={this.props.addDocTab}
-                    PanelWidth={this.props.PanelWidth}
+                    addDocTab={this._props.addDocTab}
+                    PanelWidth={this._props.PanelWidth}
                     PanelHeight={this.rowHeight}
                     ref={(function () {
                         let oldEl: KeyValuePair | undefined;
@@ -220,19 +232,19 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     getFieldView = () => {
         const rows = this.rows.filter(row => row.isChecked);
         if (rows.length > 1) {
-            const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true });
+            const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true });
             for (const row of rows) {
-                const field = this.createFieldView(DocCast(this.props.Document), row);
+                const field = this.createFieldView(DocCast(this._props.Document), row);
                 field && Doc.AddDocToList(parent, 'data', field);
                 row.uncheck();
             }
             return parent;
         }
-        return rows.length ? this.createFieldView(DocCast(this.props.Document), rows.lastElement()) : undefined;
+        return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined;
     };
 
     createFieldView = (templateDoc: Doc, row: KeyValuePair) => {
-        const metaKey = row.props.keyName;
+        const metaKey = row._props.keyName;
         const fieldTemplate = Doc.IsDelegateField(templateDoc, metaKey) ? Doc.MakeDelegate(templateDoc) : Doc.MakeEmbedding(templateDoc);
         fieldTemplate.title = metaKey;
         fieldTemplate.layout_fitWidth = true;
@@ -278,8 +290,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         openItems.push({
             description: 'Default Perspective',
             event: () => {
-                this.props.addDocTab(this.props.Document, OpenWhere.close);
-                this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
+                this._props.addDocTab(this._props.Document, OpenWhere.close);
+                this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
             },
             icon: 'image',
         });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 3cda70648..40991f371 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,7 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable, toJS } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field } from '../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
@@ -34,6 +34,18 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
     @observable public isChecked = false;
     private checkbox = React.createRef<HTMLInputElement>();
 
+    _prevProps: KeyValuePairProps;
+    @observable _props: KeyValuePairProps;
+    constructor(props: KeyValuePairProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @action
     handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.isChecked = e.currentTarget.checked;
@@ -46,24 +58,24 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
     };
 
     onContextMenu = (e: React.MouseEvent) => {
-        const value = this.props.doc[this.props.keyName];
+        const value = this._props.doc[this._props.keyName];
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
+            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this._props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     };
 
     render() {
         const props: FieldViewProps = {
-            Document: this.props.doc,
+            Document: this._props.doc,
             childFilters: returnEmptyFilter,
             childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
             styleProvider: DefaultStyleProvider,
             docViewPath: returnEmptyDoclist,
-            fieldKey: this.props.keyName,
+            fieldKey: this._props.keyName,
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
@@ -73,12 +85,11 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             whenChildContentsActiveChanged: emptyFunction,
             ScreenToLocalTransform: Transform.Identity,
             focus: emptyFunction,
-            PanelWidth: this.props.PanelWidth,
-            PanelHeight: this.props.PanelHeight,
+            PanelWidth: this._props.PanelWidth,
+            PanelHeight: this._props.PanelHeight,
             addDocTab: returnFalse,
             pinToPres: returnZero,
         };
-        const contents = <FieldView {...props} />;
         // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
         let protoCount = 0;
         let doc: Doc | undefined = props.Document;
@@ -95,8 +106,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
 
         return (
-            <tr className={this.props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
-                <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
+            <tr className={this._props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
+                <td className="keyValuePair-td-key" style={{ width: `${this._props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
                         <button
                             style={hover}
@@ -118,9 +129,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                         </Tooltip>
                     </div>
                 </td>
-                <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
+                <td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
                     <div className="keyValuePair-td-value-container">
-                        <EditableView contents={contents} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+                        <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
                     </div>
                 </td>
             </tr>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index f8140af93..52ca8b5b1 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,10 +1,11 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, override } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { copyProps } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
@@ -29,21 +30,32 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
     }
     private dropDisposer?: DragManager.DragDropDisposer;
     private _timeout: any;
+
+    _prevProps: React.PropsWithChildren<FieldViewProps & LabelBoxProps>;
+    @override _props: React.PropsWithChildren<FieldViewProps & LabelBoxProps>;
+    constructor(props: React.PropsWithChildren<FieldViewProps & LabelBoxProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
     }
     componentWillUnMount() {
         this._timeout && clearTimeout(this._timeout);
     }
 
     @computed get Title() {
-        return this.dataDoc.title_custom ? StrCast(this.Document.title) : this.props.label ? this.props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
+        return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
     }
 
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
         if (ele) {
-            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document);
+            this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document);
         }
     };
 
@@ -66,7 +78,6 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
     };
 
     @undoBatch
-    @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         const docDragData = de.complete.docDragData;
         const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
@@ -81,7 +92,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
 
     @observable _mouseOver = false;
     @computed get hoverColor() {
-        return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor);
+        return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
     }
 
     fitTextToBox = (
@@ -142,7 +153,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
                 onMouseOver={action(() => (this._mouseOver = true))}
                 ref={this.createDropTarget}
                 onContextMenu={this.specificContextMenu}
-                style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
+                style={{ boxShadow: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) }}>
                 <div
                     className="labelBox-mainButton"
                     style={{
@@ -156,8 +167,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
                         paddingRight: NumCast(this.layoutDoc._xPadding),
                         paddingTop: NumCast(this.layoutDoc._yPadding),
                         paddingBottom: NumCast(this.layoutDoc._yPadding),
-                        width: this.props.PanelWidth(),
-                        height: this.props.PanelHeight(),
+                        width: this._props.PanelWidth(),
+                        height: this._props.PanelHeight(),
                         whiteSpace: 'singleLine' in boxParams && boxParams.singleLine ? 'pre' : 'pre-wrap',
                     }}>
                     <span style={{ width: 'singleLine' in boxParams ? '' : '100%' }} ref={action((r: any) => this.fitTextToBox(r))}>
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index ed448ecfb..ff2597fb4 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -103,7 +103,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentWillUnmount(): void {
         this.disposer?.();
     }
-    @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
+    @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
     render() {
         if (this.renderProps) {
             const highlight = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 4df8e792e..dd102edef 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -144,8 +144,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 LinkManager.currentLink = this._linkDoc;
                 LinkManager.currentLinkAnchor = this._linkSrc;
                 this.props.docProps.DocumentView?.().select(false);
-                if ((SettingsManager.propertiesWidth ?? 0) < 100) {
-                    SettingsManager.propertiesWidth = 250;
+                if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
+                    SettingsManager.Instance.propertiesWidth = 250;
                 }
             })
         );
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index e554cb8ad..bbb725a3d 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -6,6 +6,7 @@ import { Doc } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { StrCast } from '../../../fields/Types';
 import { Networking } from '../../Network';
+import { DocumentManager } from '../../util/DocumentManager';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LoadingBox.scss';
@@ -36,28 +37,27 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LoadingBox, fieldKey);
     }
-    @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor
     // removes from currently loading display
     @action
     public static removeCurrentlyLoading(doc: Doc) {
-        if (LoadingBox.CurrentlyLoading) {
-            const index = LoadingBox.CurrentlyLoading.indexOf(doc);
-            index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1);
+        if (DocumentManager.Instance.CurrentlyLoading) {
+            const index = DocumentManager.Instance.CurrentlyLoading.indexOf(doc);
+            index !== -1 && DocumentManager.Instance.CurrentlyLoading.splice(index, 1);
         }
     }
 
     // adds doc to currently loading display
     @action
     public static addCurrentlyLoading(doc: Doc) {
-        if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) {
-            LoadingBox.CurrentlyLoading.push(doc);
+        if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) {
+            DocumentManager.Instance.CurrentlyLoading.push(doc);
         }
     }
 
     _timer: any;
     @observable progress = '';
     componentDidMount() {
-        if (!LoadingBox.CurrentlyLoading?.includes(this.Document)) {
+        if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) {
             this.Document.loadingError = 'Upload interrupted, please try again';
         } else {
             const updateFunc = async () => {
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 69723b171..98a302834 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,20 +1,18 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import BingMapsReact from 'bingmaps-react';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoable, UndoManager } from '../../../util/UndoManager';
 import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
@@ -68,7 +66,19 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _sidebarRef = React.createRef<SidebarAnnos>();
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
-    private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+
+    _unmounting = false;
+    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
 
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
@@ -88,7 +98,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get SidebarKey() {
         return this.fieldKey + '_sidebar';
@@ -96,10 +106,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     componentDidMount() {
         this._unmounting = false;
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
     }
 
-    _unmounting = false;
     componentWillUnmount(): void {
         this._unmounting = true;
         this.deselectPin();
@@ -161,9 +170,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             e,
             (e, down, delta) =>
                 runInAction(() => {
-                    const localDelta = this.props
+                    const localDelta = this._props
                         .ScreenToLocalTransform()
-                        .scale(this.props.NativeDimScaling?.() || 1)
+                        .scale(this._props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
                     const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
@@ -182,7 +191,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
         );
     };
-    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
 
     /**
      * Handles toggle of sidebar on click the little comment button
@@ -194,7 +203,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 key="sidebar"
                 title="Toggle Sidebar"
                 style={{
-                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    display: !this._props.isContentActive() ? 'none' : undefined,
                     top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
@@ -231,12 +240,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -271,19 +280,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return false;
     };
 
-    setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
-
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
-    pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+    pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
 
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
-    infoWidth = () => this.props.PanelWidth() / 5;
-    infoHeight = () => this.props.PanelHeight() / 5;
+    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+    infoWidth = () => this._props.PanelWidth() / 5;
+    infoHeight = () => this._props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     savedAnnotations = () => this._savedAnnotations;
 
@@ -348,7 +355,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }, 'createpin');
 
     // The pin that is selected
-    @observable selectedPin: Doc | undefined;
+    @observable selectedPin: Doc | undefined = undefined;
 
     @action
     deselectPin = () => {
@@ -398,9 +405,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
-        const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-        const y = point.y + this.props.PanelHeight() / 2 + 32;
-        const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+        const y = point.y + this._props.PanelHeight() / 2 + 32;
+        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -411,7 +418,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      */
     @action
     mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
-        this.props.select(false);
+        this._props.select(false);
         this.deselectPin();
     };
     /*
@@ -454,6 +461,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     /*
      * Returns doc w/ relevant info
      */
+
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
@@ -599,6 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
      * Initializes starting values
      */
     @observable _mapReady = false;
+
     @action
     bingMapReady = (map: any) => {
         this._mapReady = true;
@@ -676,9 +685,9 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 let target = document.elementFromPoint(e.x, e.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
-                        const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-                        const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+                        const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
                         this.createPushpin(location.latitude, location.longitude);
                         break;
@@ -717,7 +726,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return null;
         }
 
-        const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
             <div className="mapBox" ref={this._ref}>
                 <div
@@ -727,10 +735,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         e.button === 0 && !e.ctrlKey && e.stopPropagation();
                     }}
                     style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
-                    <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
-                    {renderAnnotations(this.opaqueFilter)}
-                    {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
-
                     <div className="mapBox-searchbar">
                         <EditableText
                             // editing
@@ -771,8 +775,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                   .map((pushpin, i) => (
                                       <DocumentView
                                           key={i}
-                                          {...this.props}
-                                          renderDepth={this.props.renderDepth + 1}
+                                          {...this._props}
+                                          renderDepth={this._props.renderDepth + 1}
                                           Document={pushpin}
                                           TemplateDataDocument={undefined}
                                           PanelWidth={returnOne}
@@ -799,7 +803,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
-                        {...this.props}
+                        {...this._props}
                         fieldKey={this.fieldKey}
                         Document={this.Document}
                         layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 39ed6a47e..8a6d68a71 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -101,7 +101,7 @@
 //     }
 
 //     @observable private _map: google.maps.Map = null as unknown as google.maps.Map;
-//     @observable private selectedPlace: Doc | undefined;
+//     @observable private selectedPlace: Doc | undefined = undefined;
 //     @observable private markerMap: { [id: string]: google.maps.Marker } = {};
 //     @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter;
 //     @observable private inputRef = React.createRef<HTMLInputElement>();
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 590f8735c..8760c8600 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -1,15 +1,11 @@
-import { observer } from 'mobx-react';
-// import { SettingsManager } from '../../../util/SettingsManager';
+import * as React from 'react';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
-import * as React from 'react';
-import { computed } from 'mobx';
 import { MapBox } from './MapBox';
 
 /**
  * Map Pushpin doc class
  */
-@observer
 export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(MapPushpinBox, fieldKey);
@@ -21,11 +17,11 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.mapBoxView.deletePushpin(this.Document);
     }
 
-    @computed get mapBoxView() {
-        return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as MapBox;
+    get mapBoxView() {
+        return this.props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
     }
-    @computed get mapBox() {
-        return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document;
+    get mapBox() {
+        return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
     }
 
     render() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8de8498d7..2a884cef8 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
@@ -10,7 +10,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -52,14 +52,18 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _pageControls = false;
 
     @computed get pdfUrl() {
-        return Cast(this.dataDoc[this.props.fieldKey], PdfField);
+        return Cast(this.dataDoc[this._props.fieldKey], PdfField);
     }
     @computed get pdfThumb() {
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
     }
 
-    constructor(props: any) {
+    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
         const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200;
         !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw));
@@ -68,6 +72,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf)));
         }
     }
+    componentDidUpdate() {
+        copyProps(this);
+    }
 
     replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
         if (oldDiv.childNodes) {
@@ -101,7 +108,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Doc.GetProto(region).followLinkToggle = true;
         this.addDocument(region);
 
-        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
         const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
         newDiv.style.width = NumCast(this.layoutDoc._width).toString();
         newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -110,8 +117,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const anchx = NumCast(cropping.x);
         const anchy = NumCast(cropping.y);
-        const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1);
-        const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1);
+        const anchw = NumCast(cropping._width) * (this._props.NativeDimScaling?.() || 1);
+        const anchh = NumCast(cropping._height) * (this._props.NativeDimScaling?.() || 1);
         const viewScale = 1;
         cropping.title = 'crop: ' + this.Document.title;
         cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
@@ -131,7 +138,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
-        this.props.bringToFront(cropping);
+        this._props.bringToFront(cropping);
 
         CreateImage(
             '',
@@ -139,8 +146,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             htmlString,
             anchw,
             anchh,
-            (NumCast(region.y) * this.props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
-            (NumCast(region.x) * this.props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
+            (NumCast(region.y) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
+            (NumCast(region.x) * this._props.PanelWidth()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']),
             4
         )
             .then((data_url: any) => {
@@ -162,7 +169,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     updateIcon = () => {
         // currently we render pdf icons as text labels
-        const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+        const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
         const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
         this._pdfViewer?._mainCont.current &&
             CollectionFreeFormView.UpdateIcon(
@@ -170,8 +177,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 docViewContent,
                 NumCast(this.layoutDoc._width),
                 NumCast(this.layoutDoc._height),
-                this.props.PanelWidth(),
-                this.props.PanelHeight(),
+                this._props.PanelWidth(),
+                this._props.PanelHeight(),
                 NumCast(this.layoutDoc._layout_scrollTop),
                 NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], 1),
                 true,
@@ -190,20 +197,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         this._disposers.select = reaction(
-            () => this.props.isSelected(),
+            () => this._props.isSelected(),
             () => {
                 document.removeEventListener('keydown', this.onKeyDown);
-                this.props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
+                this._props.isSelected() && document.addEventListener('keydown', this.onKeyDown);
             },
             { fireImmediately: true }
         );
         this._disposers.scroll = reaction(
             () => this.layoutDoc.layout_scrollTop,
             () => {
-                if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
-                    this.props.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
+                if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) {
+                    this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop');
                 }
                 this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1;
                 this.layoutDoc[this.SidebarKey + '_freeform_panX'] = 0;
@@ -212,11 +219,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
 
     sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
-        if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+        if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(false);
             return true;
         }
-        return this.props.addDocTab(doc, where);
+        return this._props.addDocTab(doc, where);
     };
     focus = (anchor: Doc, options: DocFocusOptions) => {
         this._initialScrollTarget = anchor;
@@ -251,7 +258,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     loaded = (nw: number, nh: number, np: number) => {
-        this.dataDoc[this.props.fieldKey + '_numPages'] = np;
+        this.dataDoc[this._props.fieldKey + '_numPages'] = np;
         Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72));
         Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72);
         this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1);
@@ -276,7 +283,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return true;
     };
     public forwardPage = () => {
-        this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
+        this.Document._layout_curPage = Math.min(NumCast(this.dataDoc[this._props.fieldKey + '_numPages']), (NumCast(this.Document._layout_curPage) || 1) + 1);
         return true;
     };
     public gotoPage = (p: number) => (this.Document._layout_curPage = p);
@@ -300,7 +307,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     setPdfViewer = (pdfViewer: PDFViewer) => {
         this._pdfViewer = pdfViewer;
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         if (this._initialScrollTarget && docView) {
             this.focus(this._initialScrollTarget, { instant: true });
             this._initialScrollTarget = undefined;
@@ -321,13 +328,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this,
             e,
             (e, down, delta) => {
-                const localDelta = this.props
+                const localDelta = this._props
                     .ScreenToLocalTransform()
-                    .scale(this.props.NativeDimScaling?.() || 1)
+                    .scale(this._props.NativeDimScaling?.() || 1)
                     .transformDirection(delta[0], delta[1]);
                 const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
                 const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]);
@@ -372,12 +379,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
         const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`;
         const curPage = NumCast(this.Document._layout_curPage) || 1;
-        return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
+        return !this._props.isContentActive() || this._pdfViewer?.isAnnotating ? null : (
             <div
                 className="pdfBox-ui"
                 onKeyDown={e => ([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)}
                 onPointerDown={e => e.stopPropagation()}
-                style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+                style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
                 <div className="pdfBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                     <button className="pdfBox-overlayButton" title={searchTitle} />
                     <input
@@ -431,7 +438,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (!this.SidebarShown) return 0;
         if (this._previewWidth) return PDFBox.sidebarResizerWidth + PDFBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
         const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
-        return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
+        return PDFBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
     };
     @undoBatch
     toggleSidebarType = () => (this.dataDoc[this.SidebarKey + '_type_collection'] = this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform);
@@ -450,11 +457,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     @computed get renderTitleBox() {
-        const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : '');
+        const classname = 'pdfBox' + (this._props.isContentActive() ? '-interactive' : '');
         return (
             <div className={classname}>
                 <div className="pdfBox-title-outer">
-                    <strong className="pdfBox-title">{StrCast(this.props.Document.title)}</strong>
+                    <strong className="pdfBox-title">{StrCast(this.Document.title)}</strong>
                 </div>
             </div>
         );
@@ -472,7 +479,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 key="sidebar"
                 title="Toggle Sidebar"
                 style={{
-                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    display: !this._props.isContentActive() ? 'none' : undefined,
                     top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
@@ -489,26 +496,26 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
         const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
         const pdfRatio = pdfNativeWidth / nativeWidth;
-        return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
+        return (pdfRatio * this._props.PanelWidth()) / pdfNativeWidth;
     }
     @computed get sidebarNativeWidth() {
         return this.sidebarWidth() / this.pdfScale;
     }
     @computed get sidebarNativeHeight() {
-        return this.props.PanelHeight() / this.pdfScale;
+        return this._props.PanelHeight() / this.pdfScale;
     }
     sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
     sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
     sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
-    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
+    sidebarScreenToLocal = () => this._props.ScreenToLocalTransform().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
             const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
-                    {...this.props}
+                    {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
@@ -521,13 +528,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     removeDocument={this.removeDocument}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
                     <ComponentTag
-                        {...this.props}
+                        {...this._props}
                         setContentView={emptyFunction} // override setContentView to do nothing
                         NativeWidth={this.sidebarNativeWidthFunc}
                         NativeHeight={this.sidebarNativeHeightFunc}
-                        PanelHeight={this.props.PanelHeight}
+                        PanelHeight={this._props.PanelHeight}
                         PanelWidth={this.sidebarWidth}
                         xPadding={0}
                         yPadding={0}
@@ -541,7 +548,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
-                        renderDepth={this.props.renderDepth + 1}
+                        renderDepth={this._props.renderDepth + 1}
                         noSidebar={true}
                         fieldKey={this.SidebarKey}
                     />
@@ -557,13 +564,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
-        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+        const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
         return !this._pdf ? null : (
             <div
                 className="pdfBox"
                 onContextMenu={this.specificContextMenu}
                 style={{
-                    height: this.props.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined,
+                    height: this.Document._layout_scrollTop && !this.Document._layout_fitWidth && window.screen.width > 600 ? (NumCast(this.Document._height) * this._props.PanelWidth()) / NumCast(this.Document._width) : undefined,
                 }}>
                 <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} />
                 <div
@@ -576,7 +583,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         top: 0,
                     }}>
                     <PDFViewer
-                        {...this.props}
+                        {...this._props}
                         sidebarAddDoc={this.sidebarAddDocument}
                         addDocTab={this.sidebarAddDocTab}
                         layoutDoc={this.layoutDoc}
@@ -594,7 +601,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         crop={this.crop}
                     />
                 </div>
-                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
                 {this.settingsPanel()}
             </div>
         );
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 7c21b1893..a7ff8ff8f 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -43,7 +43,7 @@ declare class MediaRecorder {
 
 // @observer
 // export class VideoTile extends React.Component<VideoTileProps> {
-//     @observable _videoRef: HTMLVideoElement | undefined;
+//     @observable _videoRef: HTMLVideoElement | undefined = undefined;
 //     _mesh: any = undefined;
 
 //     render() {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 04780155d..2dad115e1 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { basename } from 'path';
 import { Doc, StrListCast } from '../../../fields/Doc';
@@ -9,7 +9,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -33,7 +33,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import { PinProps, PresBox } from './trails';
 import './VideoBox.scss';
-const path = require('path');
 
 /**
  * VideoBox
@@ -52,7 +51,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(VideoBox, fieldKey);
     }
-
     static _youtubeIframeCounter: number = 0;
     static heightPercent = 80; // height of video relative to videoBox when timeline is open
     static numThumbnails = 20;
@@ -70,6 +68,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _playRegionTimer: any = null; // timeout for playback
     private _controlsFadeTimer: any = null; // timeout for controls fade
 
+    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._props.setContentView?.(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable _stackedTimeline: any; // CollectionStackedTimeline ref
     @observable static _nativeControls: boolean; // default html controls
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@@ -96,13 +107,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @observable rawDuration: number = 0;
 
     @computed get youtubeVideoId() {
-        const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+        const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
         return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : '';
     }
 
     // returns the path of the audio file
     @computed get audiopath() {
-        const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null);
+        const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null);
         const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
         return field?.url.href ?? vfield?.url.href ?? '';
     }
@@ -125,7 +136,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     componentDidMount() {
         this.unmounting = false;
-        this.props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+        this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
         if (this.youtubeVideoId) {
             const youtubeaspect = 400 / 315;
             const nativeWidth = Doc.NativeWidth(this.layoutDoc);
@@ -162,7 +173,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (
             // need to include range inputs because after dragging time slider it becomes target element
             !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
-            this.props.isSelected()
+            this._props.isSelected()
         ) {
             switch (e.key) {
                 case 'ArrowLeft':
@@ -267,7 +278,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this.player && this._contentRef && this._contentRef.requestFullscreen();
         }
         try {
-            this._youtubePlayer && this.props.addDocTab(this.Document, OpenWhere.add);
+            this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add);
         } catch (e) {
             console.log('Video FullScreen Exception:', e);
         }
@@ -328,7 +339,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 title: (this.layoutDoc._layout_currentTimecode || 0).toString(),
                 onClick: FollowLinkScript(),
             });
-            this.props.addDocument?.(b);
+            this._props.addDocument?.(b);
             DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' });
             Networking.PostToServer('/youtubeScreenshot', {
                 id: this.youtubeVideoId,
@@ -336,7 +347,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             }).then(response => {
                 const resolved = response?.accessPaths?.agnostic?.client;
                 if (resolved) {
-                    this.props.removeDocument?.(b);
+                    this._props.removeDocument?.(b);
                     this.createSnapshotLink(resolved);
                 }
             });
@@ -377,7 +388,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         });
         Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
         Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
-        this.props.addDocument?.(imageSnapshot);
+        this._props.addDocument?.(imageSnapshot);
         const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
         link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
         setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
@@ -492,7 +503,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // context menu
     specificContextMenu = (e: React.MouseEvent): void => {
-        const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+        const field = Cast(this.dataDoc[this._props.fieldKey], VideoField);
         if (field) {
             const url = field.url.href;
             const subitems: ContextMenuProps[] = [];
@@ -532,7 +543,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     event: () => {
                         this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey);
                         // delete assoicated video data
-                        this.dataDoc[this.props.fieldKey] = '';
+                        this.dataDoc[this._props.fieldKey] = '';
                         this.dataDoc[this.fieldKey + '_duration'] = '';
                         // delete assoicated presentation data
                         this.dataDoc[this.fieldKey + '_presentation'] = '';
@@ -550,7 +561,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // renders the video and audio
     @computed get content() {
         const field = Cast(this.dataDoc[this.fieldKey], VideoField);
-        const interactive = Doc.ActiveTool !== InkTool.None || !this.props.isSelected() ? '' : '-interactive';
+        const interactive = Doc.ActiveTool !== InkTool.None || !this._props.isSelected() ? '' : '-interactive';
         const classname = 'videoBox-content' + (this._fullScreen ? '-fullScreen' : '') + interactive;
         const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0;
         return !field ? (
@@ -586,7 +597,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         Not supported.
                     </video>
                     {!this.audiopath || this.audiopath === field.url.href ? null : (
-                        <audio ref={this.setAudioRef} className={`audiobox-control${this.props.isContentActive() ? '-interactive' : ''}`}>
+                        <audio ref={this.setAudioRef} className={`audiobox-control${this._props.isContentActive() ? '-interactive' : ''}`}>
                             <source src={this.audiopath} type="audio/mpeg" />
                             Not supported.
                         </audio>
@@ -626,7 +637,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
             );
             this._disposers.youtubeReactionDisposer = reaction(
-                () => Doc.ActiveTool === InkTool.None && this.props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
+                () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting,
                 interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
                 { fireImmediately: true }
             );
@@ -636,8 +647,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             (YT as any)?.ready(() => {
                 this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, {
                     events: {
-                        onReady: this.props.dontRegisterView ? undefined : onYoutubePlayerReady,
-                        onStateChange: this.props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
+                        onReady: this._props.dontRegisterView ? undefined : onYoutubePlayerReady,
+                        onStateChange: this._props.dontRegisterView ? undefined : onYoutubePlayerStateChange,
                     },
                 });
             });
@@ -677,9 +688,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             e,
             action(encodeURIComponent => {
                 this._clicking = false;
-                if (this.props.isContentActive()) {
-                    // const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
-                    // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+                if (this._props.isContentActive()) {
+                    // const local = this._props.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+                    // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
 
                     this.layoutDoc._layout_timelineHeightPercent = 80;
                 }
@@ -693,15 +704,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     500
                 );
             },
-            this.props.isContentActive(),
-            this.props.isContentActive()
+            this._props.isContentActive(),
+            this._props.isContentActive()
         );
     };
 
     // removes from currently playing display
     @action
     removeCurrentlyPlaying = () => {
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
             const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
             index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -710,7 +721,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     // adds doc to currently playing display
     @action
     addCurrentlyPlaying = () => {
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         if (!CollectionStackedTimeline.CurrentlyPlaying) {
             CollectionStackedTimeline.CurrentlyPlaying = [];
         }
@@ -861,7 +872,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // starts marquee selection
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -882,31 +893,31 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @action
     finishMarquee = () => {
         this._marqueeref.current?.onTerminateSelection();
-        this.props.select(true);
+        this._props.select(true);
     };
 
-    timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
+    timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
 
     timelineScreenToLocal = () =>
-        this.props
+        this._props
             .ScreenToLocalTransform()
             .scale(this.scaling())
-            .translate(0, (-this.heightPercent / 100) * this.props.PanelHeight());
+            .translate(0, (-this.heightPercent / 100) * this._props.PanelHeight());
 
     setPlayheadTime = (time: number) => (this.player!.currentTime = this.layoutDoc._layout_currentTimecode = time);
 
-    timelineHeight = () => (this.props.PanelHeight() * (100 - this.heightPercent)) / 100;
+    timelineHeight = () => (this._props.PanelHeight() * (100 - this.heightPercent)) / 100;
 
     playing = () => this._playing;
 
-    scaling = () => this.props.NativeDimScaling?.() || 1;
+    scaling = () => this._props.NativeDimScaling?.() || 1;
 
-    panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
-    panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this.props.PanelHeight() * this.heightPercent) / 100);
+    panelWidth = () => (this._props.PanelWidth() * this.heightPercent) / 100;
+    panelHeight = () => (this.layoutDoc._layout_fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.dataDoc) || 1) : (this._props.PanelHeight() * this.heightPercent) / 100);
 
     screenToLocalTransform = () => {
-        const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
-        return this.props
+        const offset = (this._props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
+        return this._props
             .ScreenToLocalTransform()
             .translate(-offset, 0)
             .scale(100 / this.heightPercent);
@@ -918,10 +929,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // renders video controls
     componentUI = (boundsLeft: number, boundsTop: number) => {
-        const xf = this.props.ScreenToLocalTransform().inverse();
-        const height = this.props.PanelHeight();
+        const xf = this._props.ScreenToLocalTransform().inverse();
+        const height = this._props.PanelHeight();
         const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
-        const vidWidth = this.props.PanelWidth() / this.scaling();
+        const vidWidth = this._props.PanelWidth() / this.scaling();
         const uiHeight = 25;
         const uiMargin = 10;
         const yBot = xf.transformPoint(0, vidHeight)[1];
@@ -936,7 +947,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <div
                     className="videoBox-ui"
                     style={{
-                        transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${this._props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
@@ -957,13 +968,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%`, display: this.heightPercent === 100 ? 'none' : '' }}>
                 <CollectionStackedTimeline
                     ref={action((r: any) => (this._stackedTimeline = r))}
-                    {...this.props}
+                    {...this._props}
                     dataFieldKey={this.fieldKey}
                     fieldKey={this.annotationKey}
                     dictationKey={this.fieldKey + '_dictation'}
                     mediaPath={this.audiopath}
                     thumbnails={this.thumbnails}
-                    renderDepth={this.props.renderDepth + 1}
+                    renderDepth={this._props.renderDepth + 1}
                     startTag={'_timecodeToShow' /* videoStart */}
                     endTag={'_timecodeToHide' /* videoEnd */}
                     bringToFront={emptyFunction}
@@ -1011,8 +1022,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         cropping.title = 'crop: ' + this.Document.title;
         cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
         cropping.y = NumCast(this.Document.y);
-        cropping._width = anchw * (this.props.NativeDimScaling?.() || 1);
-        cropping._height = anchh * (this.props.NativeDimScaling?.() || 1);
+        cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
+        cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
         cropping.timecodeToHide = undefined;
         cropping.timecodeToShow = undefined;
         cropping.onClick = undefined;
@@ -1038,12 +1049,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (addCrop) {
             DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
         }
-        this.props.bringToFront(cropping);
+        this._props.bringToFront(cropping);
         return cropping;
     };
     savedAnnotations = () => this._savedAnnotations;
     render() {
-        const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
+        const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / this.scaling()}px` : borderRad;
         return (
             <div
@@ -1053,7 +1064,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 style={{
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
-                    overflow: this.props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
+                    overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
                 }}>
                 <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
                     <div
@@ -1063,19 +1074,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             width: this.panelWidth(),
                             height: this.panelHeight(),
                             top: 0,
-                            left: (this.props.PanelWidth() - this.panelWidth()) / 2,
+                            left: (this._props.PanelWidth() - this.panelWidth()) / 2,
                         }}>
                         <CollectionFreeFormView
-                            {...this.props}
+                            {...this._props}
                             setContentView={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
-                            renderDepth={this.props.renderDepth + 1}
+                            renderDepth={this._props.renderDepth + 1}
                             fieldKey={this.annotationKey}
                             isAnnotationOverlay={true}
                             annotationLayerHostsContent={true}
-                            PanelWidth={this.props.PanelWidth}
-                            PanelHeight={this.props.PanelHeight}
+                            PanelWidth={this._props.PanelWidth}
+                            PanelHeight={this._props.PanelHeight}
                             isAnyChildContentActive={returnFalse}
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             childFilters={this.timelineDocFilter}
@@ -1097,8 +1108,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             scrollTop={0}
                             annotationLayerScrollTop={0}
                             scaling={returnOne}
-                            annotationLayerScaling={this.props.NativeDimScaling}
-                            docView={this.props.DocumentView!}
+                            annotationLayerScaling={this._props.NativeDimScaling}
+                            docView={this._props.DocumentView!}
                             containerOffset={this.marqueeOffset}
                             addDocument={this.addDocWithTimecode}
                             finishMarquee={this.finishMarquee}
@@ -1116,7 +1127,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     }
 
     @computed get UIButtons() {
-        const bounds = this.props.docViewPath().lastElement().getBounds();
+        const bounds = this._props.docViewPath().lastElement().getBounds();
         const width = (bounds?.right || 0) - (bounds?.left || 0);
         const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
         return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d02e5f3e8..c722399c1 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -67,7 +67,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable private _searching: boolean = false;
     @observable private _showSidebar = false;
     @observable private _webPageHasBeenRendered = false;
-    @observable private _marqueeing: number[] | undefined;
+    @observable private _marqueeing: number[] | undefined = undefined;
     get marqueeing() {
         return this._marqueeing;
     }
@@ -403,7 +403,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const locpt = { 
             x: (e.clientX / NumCast(this.Document.nativeWidth)) * this.props.PanelWidth(), 
             y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
-        const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+        const scrclick = this.props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
         const scrcent = this.props
             .DocumentView?.()
             .props.ScreenToLocalTransform()
diff --git a/src/client/views/nodes/audio/AudioWaveform.scss b/src/client/views/nodes/audio/AudioWaveform.scss
new file mode 100644
index 000000000..6cbd1759a
--- /dev/null
+++ b/src/client/views/nodes/audio/AudioWaveform.scss
@@ -0,0 +1,17 @@
+.audioWaveform  {
+    position: relative;
+    width: 100%;
+    height: 200%;
+    overflow: hidden;
+    z-index: -1000;
+    bottom: 0;
+    pointer-events: none;
+    div {
+        height: 100% !important;
+        width: 100%  !important;
+    }
+    canvas { 
+        height: 100% !important;
+        width: 100%  !important;
+    }
+}
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
new file mode 100644
index 000000000..1b1a85800
--- /dev/null
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -0,0 +1,127 @@
+import axios from 'axios';
+import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, NumListCast } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { listSpec } from '../../../../fields/Schema';
+import { Cast } from '../../../../fields/Types';
+import { copyProps, numberRange } from '../../../../Utils';
+import { Colors } from './../../global/globalEnums';
+import './AudioWaveform.scss';
+import { WaveCanvas } from './WaveCanvas';
+
+/**
+ * AudioWaveform
+ *
+ * Used in CollectionStackedTimeline to render a canvas with a visual of an audio waveform for AudioBox and VideoBox documents.
+ * Uses react-audio-waveform package.
+ * Bins the audio data into audioBuckets which are passed to package to render the lines.
+ * Calculates new buckets each time a new zoom factor or new set of trim bounds is created and stores it in a field on the layout doc with a title indicating the bounds and zoom for that list (see audioBucketField)
+ */
+
+export interface AudioWaveformProps {
+    duration: number; // length of media clip
+    rawDuration: number; // length of underlying media data
+    mediaPath: string;
+    layoutDoc: Doc;
+    clipStart: number;
+    clipEnd: number;
+    zoomFactor: number;
+    PanelHeight: number;
+    PanelWidth: number;
+    fieldKey: string;
+    progress?: number;
+}
+
+@observer
+export class AudioWaveform extends React.Component<AudioWaveformProps> {
+    public static NUMBER_OF_BUCKETS = 100; // number of buckets data is divided into to draw waveform lines
+    _disposer: IReactionDisposer | undefined;
+    _prevProps: React.PropsWithChildren<AudioWaveformProps>;
+    @observable _props: React.PropsWithChildren<AudioWaveformProps>;
+    constructor(props: React.PropsWithChildren<AudioWaveformProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
+    get waveHeight() {
+        return Math.max(50, this._props.PanelHeight);
+    }
+
+    get clipStart() {
+        return this._props.clipStart;
+    }
+    get clipEnd() {
+        return this._props.clipEnd;
+    }
+    get zoomFactor() {
+        return this._props.zoomFactor;
+    }
+
+    @computed get audioBuckets() {
+        return NumListCast(this._props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)]);
+    }
+
+    audioBucketField = (start: number, end: number, zoomFactor: number) => this._props.fieldKey + '_audioBuckets/' + '/' + start.toFixed(2).replace('.', '_') + '/' + end.toFixed(2).replace('.', '_') + '/' + zoomFactor * 10;
+
+    componentWillUnmount() {
+        this._disposer?.();
+    }
+
+    componentDidMount() {
+        this._disposer = reaction(
+            () => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this._props.zoomFactor }),
+            ({ clipStart, clipEnd, fieldKey, zoomFactor }) => {
+                if (!this._props.layoutDoc[fieldKey] && this._props.layoutDoc.layout_fieldKey != 'layout_icon') {
+                    // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time.
+                    const waveform = Cast(this._props.layoutDoc[this.audioBucketField(0, this._props.rawDuration, 1)], listSpec('number'));
+                    this._props.layoutDoc[fieldKey] = waveform && new List<number>(waveform.slice((clipStart / this._props.rawDuration) * waveform.length, (clipEnd / this._props.rawDuration) * waveform.length));
+                    setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor));
+                }
+            },
+            { fireImmediately: true }
+        );
+    }
+
+    // decodes the audio file into peaks for generating the waveform
+    createWaveformBuckets = (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => {
+        axios({ url: this._props.mediaPath, responseType: 'arraybuffer' }).then(response =>
+            new window.AudioContext().decodeAudioData(response.data, buffer => {
+                const rawDecodedAudioData = buffer.getChannelData(0);
+                const startInd = clipStart / this._props.rawDuration;
+                const endInd = clipEnd / this._props.rawDuration;
+                const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length));
+                const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor);
+
+                const bucketDataSize = Math.floor(decodedAudioData.length / numBuckets);
+                const brange = Array.from(Array(bucketDataSize));
+                const bucketList = numberRange(numBuckets).map((i: number) => brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2);
+                this._props.layoutDoc[fieldKey] = new List<number>(bucketList);
+            })
+        );
+    };
+
+    render() {
+        return (
+            <div className="audioWaveform">
+                <WaveCanvas
+                    color={Colors.LIGHT_GRAY}
+                    progressColor={Colors.MEDIUM_BLUE_ALT}
+                    progress={this._props.progress ?? 1}
+                    barWidth={200 / this.audioBuckets.length}
+                    //gradientColors={this.props.gradientColors}
+                    peaks={this.audioBuckets}
+                    width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
+                    height={this.waveHeight * window.devicePixelRatio}
+                    pixelRatio={window.devicePixelRatio}
+                />
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/nodes/audio/WaveCanvas.tsx b/src/client/views/nodes/audio/WaveCanvas.tsx
new file mode 100644
index 000000000..d3f5669a2
--- /dev/null
+++ b/src/client/views/nodes/audio/WaveCanvas.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+
+interface WaveCanvasProps {
+    barWidth: number;
+    color: string;
+    progress: number;
+    progressColor: string;
+    gradientColors?: { stopPosition: number; color: string }[]; // stopPosition between 0 and 1
+    peaks: number[];
+    width: number;
+    height: number;
+    pixelRatio: number;
+}
+
+export class WaveCanvas extends React.Component<WaveCanvasProps> {
+    // If the first value of peaks is negative, addToIndices will be 1
+    posPeaks = (peaks: number[], addToIndices: number) => peaks.filter((_, index) => (index + addToIndices) % 2 == 0);
+
+    drawBars = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+        // Bar wave draws the bottom only as a reflection of the top,
+        // so we don't need negative values
+        const posPeaks = peaks.some(val => val < 0) ? this.posPeaks(peaks, peaks[0] < 0 ? 1 : 0) : peaks;
+
+        // A half-pixel offset makes lines crisp
+        const $ = 0.5 / this.props.pixelRatio;
+        const bar = this.props.barWidth * this.props.pixelRatio;
+        const gap = Math.max(this.props.pixelRatio, 2);
+
+        const max = Math.max(...posPeaks);
+        const scale = posPeaks.length / width;
+
+        for (let i = 0; i < width; i += bar + gap) {
+            if (i > width * this.props.progress) waveCanvasCtx.fillStyle = this.props.color;
+
+            const h = Math.round((posPeaks[Math.floor(i * scale)] / max) * halfH) || 1;
+
+            waveCanvasCtx.fillRect(i + $, halfH - h, bar + $, h * 2);
+        }
+    };
+
+    addNegPeaks = (peaks: number[]) => 
+         peaks.reduce((reflectedPeaks, peak) => reflectedPeaks.push(peak, -peak) ? reflectedPeaks:[], 
+                            [] as number[]); // prettier-ignore
+
+    drawWaves = (waveCanvasCtx: CanvasRenderingContext2D, width: number, halfH: number, peaks: number[]) => {
+        const allPeaks = peaks.some(val => val < 0) ? peaks : this.addNegPeaks(peaks); // add negative peaks to arrays without negative peaks
+
+        // A half-pixel offset makes lines crisp
+        const $ = 0.5 / this.props.pixelRatio;
+        const length = ~~(allPeaks.length / 2); // ~~ is Math.floor for positive numbers.
+
+        const scale = width / length;
+        const absmax = Math.max(...allPeaks.map(peak => Math.abs(peak)));
+
+        waveCanvasCtx.beginPath();
+        waveCanvasCtx.moveTo($, halfH);
+
+        for (var i = 0; i < length; i++) {
+            var h = Math.round((allPeaks[2 * i] / absmax) * halfH);
+            waveCanvasCtx.lineTo(i * scale + $, halfH - h);
+        }
+
+        // Draw the bottom edge going backwards, to make a single closed hull to fill.
+        for (var i = length - 1; i >= 0; i--) {
+            var h = Math.round((allPeaks[2 * i + 1] / absmax) * halfH);
+            waveCanvasCtx.lineTo(i * scale + $, halfH - h);
+        }
+
+        waveCanvasCtx.fill();
+
+        // Always draw a median line
+        waveCanvasCtx.fillRect(0, halfH - $, width, $);
+    };
+
+    updateSize = (width: number, height: number, peaks: number[], waveCanvasCtx: CanvasRenderingContext2D) => {
+        const displayWidth = Math.round(width / this.props.pixelRatio);
+        const displayHeight = Math.round(height / this.props.pixelRatio);
+        waveCanvasCtx.canvas.width = width;
+        waveCanvasCtx.canvas.height = height;
+        waveCanvasCtx.canvas.style.width = `${displayWidth}px`;
+        waveCanvasCtx.canvas.style.height = `${displayHeight}px`;
+
+        waveCanvasCtx.clearRect(0, 0, width, height);
+
+        const gradient = this.props.gradientColors && waveCanvasCtx.createLinearGradient(0, 0, width, 0);
+        gradient && this.props.gradientColors?.forEach(color => gradient.addColorStop(color.stopPosition, color.color));
+        waveCanvasCtx.fillStyle = gradient ?? this.props.progressColor;
+
+        const waveDrawer = this.props.barWidth ? this.drawBars : this.drawWaves;
+        waveDrawer(waveCanvasCtx, width, height / 2, peaks);
+    };
+
+    render() {
+        return this.props.peaks ? (
+            <div style={{ position: 'relative', width: '100%', height: '100%', cursor: 'pointer' }}>
+                <canvas ref={instance => (ctx => ctx && this.updateSize(this.props.width, this.props.height, this.props.peaks, ctx))(instance?.getContext('2d'))} />
+            </div>
+        ) : null;
+    }
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 4384c8958..6332b200d 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -82,7 +82,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     _spanRef = React.createRef<HTMLDivElement>();
     _disposers: { [name: string]: IReactionDisposer } = {};
     _textBox: FormattedTextBox;
-    @observable _dashDoc: Doc | undefined;
+    @observable _dashDoc: Doc | undefined = undefined;
     @observable _finalLayout: any;
     @observable _width: number = 0;
     @observable _height: number = 0;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index a395296d0..19e14d5a7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -25,7 +25,7 @@ export class DashFieldView {
     node: any;
     tbox: FormattedTextBox;
 
-    unclickable = () => !this.tbox.props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+    unclickable = () => !this.tbox._props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
     constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
         this.node = node;
         this.tbox = tbox;
@@ -97,13 +97,13 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     _textBoxDoc: Doc;
     _fieldKey: string;
     _fieldStringRef = React.createRef<HTMLSpanElement>();
-    @observable _dashDoc: Doc | undefined;
+    @observable _dashDoc: Doc | undefined = undefined;
     @observable _expanded = false;
 
     constructor(props: IDashFieldViewInternal) {
         super(props);
         this._fieldKey = this.props.fieldKey;
-        this._textBoxDoc = this.props.tbox.props.Document;
+        this._textBoxDoc = this.props.tbox.Document;
 
         if (this.props.docId) {
             DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
@@ -126,7 +126,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
                     maxWidth={this.props.hideKey ? undefined : this.return100}
-                    columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero}
+                    columnWidth={this.props.hideKey ? () => this.props.tbox._props.PanelWidth() - 20 : returnZero}
                     selectedCell={() => [this._dashDoc!, 0]}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
@@ -145,7 +145,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     }
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement();
+        let container = this.props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
@@ -157,7 +157,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
             list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
             embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
-            this.props.tbox.props.addDocTab(embedding, OpenWhere.addRight);
+            this.props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
         }
     };
 
@@ -177,7 +177,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
                 style={{
                     width: this.props.width,
                     height: this.props.height,
-                    pointerEvents: this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+                    pointerEvents: this.props.tbox._props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
                 {this.props.hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
diff --git a/src/client/views/nodes/formattedText/EquationEditor.scss b/src/client/views/nodes/formattedText/EquationEditor.scss
new file mode 100644
index 000000000..b0c17e56e
--- /dev/null
+++ b/src/client/views/nodes/formattedText/EquationEditor.scss
@@ -0,0 +1,468 @@
+// using this import, we get runtime errors when trying to load the specified font-faces
+// so we copy the .css and remove the @font-face imports
+
+// @import 'mathquill/build/mathquill.css'
+/*
+ * MathQuill v0.10.1               http://mathquill.com
+ * by Han, Jeanine, and Mary  maintainers@mathquill.com
+ *
+ * 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/.
+ */
+// @font-face {
+//     font-family: Symbola;
+//     src: url(font/Symbola.eot);
+//     src:
+//         local('Symbola Regular'),
+//         local('Symbola'),
+//         url(font/Symbola.woff2) format('woff2'),
+//         url(font/Symbola.woff) format('woff'),
+//         url(font/Symbola.ttf) format('truetype'),
+//         url(font/Symbola.otf) format('opentype'),
+//         url(font/Symbola.svg#Symbola) format('svg');
+// }
+.mq-editable-field {
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-editable-field .mq-cursor {
+    border-left: 1px solid black;
+    margin-left: -1px;
+    position: relative;
+    z-index: 1;
+    padding: 0;
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-editable-field .mq-cursor.mq-blink {
+    visibility: hidden;
+}
+.mq-editable-field,
+.mq-math-mode .mq-editable-field {
+    border: 1px solid gray;
+}
+.mq-editable-field.mq-focused,
+.mq-math-mode .mq-editable-field.mq-focused {
+    -webkit-box-shadow:
+        #8bd 0 0 1px 2px,
+        inset #6ae 0 0 2px 0;
+    -moz-box-shadow:
+        #8bd 0 0 1px 2px,
+        inset #6ae 0 0 2px 0;
+    box-shadow:
+        #8bd 0 0 1px 2px,
+        inset #6ae 0 0 2px 0;
+    border-color: #709ac0;
+    border-radius: 1px;
+}
+.mq-math-mode .mq-editable-field {
+    margin: 1px;
+}
+.mq-editable-field .mq-latex-command-input {
+    color: inherit;
+    font-family: 'Courier New', monospace;
+    border: 1px solid gray;
+    padding-right: 1px;
+    margin-right: 1px;
+    margin-left: 2px;
+}
+.mq-editable-field .mq-latex-command-input.mq-empty {
+    background: transparent;
+}
+.mq-editable-field .mq-latex-command-input.mq-hasCursor {
+    border-color: ActiveBorder;
+}
+.mq-editable-field.mq-empty:after,
+.mq-editable-field.mq-text-mode:after,
+.mq-math-mode .mq-empty:after {
+    visibility: hidden;
+    content: 'c';
+}
+.mq-editable-field .mq-cursor:only-child:after,
+.mq-editable-field .mq-textarea + .mq-cursor:last-child:after {
+    visibility: hidden;
+    content: 'c';
+}
+.mq-editable-field .mq-text-mode .mq-cursor:only-child:after {
+    content: '';
+}
+.mq-editable-field.mq-text-mode {
+    overflow-x: auto;
+    overflow-y: hidden;
+}
+.mq-root-block,
+.mq-math-mode .mq-root-block {
+    display: -moz-inline-box;
+    display: inline-block;
+    width: 100%;
+    padding: 2px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    white-space: nowrap;
+    overflow: hidden;
+    vertical-align: middle;
+}
+.mq-math-mode {
+    font-variant: normal;
+    font-weight: normal;
+    font-style: normal;
+    font-size: 115%;
+    line-height: 1;
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-math-mode .mq-non-leaf,
+.mq-math-mode .mq-scaled {
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-math-mode var,
+.mq-math-mode .mq-text-mode,
+.mq-math-mode .mq-nonSymbola {
+    font-family: 'Times New Roman', Symbola, serif;
+    line-height: 0.9;
+}
+.mq-math-mode * {
+    font-size: inherit;
+    line-height: inherit;
+    margin: 0;
+    padding: 0;
+    border-color: black;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    user-select: none;
+    box-sizing: border-box;
+}
+.mq-math-mode .mq-empty {
+    background: #ccc;
+}
+.mq-math-mode .mq-empty.mq-root-block {
+    background: transparent;
+}
+.mq-math-mode.mq-empty {
+    background: transparent;
+}
+.mq-math-mode .mq-text-mode {
+    display: inline-block;
+}
+.mq-math-mode .mq-text-mode.mq-hasCursor {
+    box-shadow: inset darkgray 0 0.1em 0.2em;
+    padding: 0 0.1em;
+    margin: 0 -0.1em;
+    min-width: 1ex;
+}
+.mq-math-mode .mq-font {
+    font:
+        1em 'Times New Roman',
+        Symbola,
+        serif;
+}
+.mq-math-mode .mq-font * {
+    font-family: inherit;
+    font-style: inherit;
+}
+.mq-math-mode b,
+.mq-math-mode b.mq-font {
+    font-weight: bolder;
+}
+.mq-math-mode var,
+.mq-math-mode i,
+.mq-math-mode i.mq-font {
+    font-style: italic;
+}
+.mq-math-mode var.mq-f {
+    margin-right: 0.2em;
+    margin-left: 0.1em;
+}
+.mq-math-mode .mq-roman var.mq-f {
+    margin: 0;
+}
+.mq-math-mode big {
+    font-size: 200%;
+}
+.mq-math-mode .mq-int > big {
+    display: inline-block;
+    -webkit-transform: scaleX(0.7);
+    -moz-transform: scaleX(0.7);
+    -ms-transform: scaleX(0.7);
+    -o-transform: scaleX(0.7);
+    transform: scaleX(0.7);
+    vertical-align: -0.16em;
+}
+.mq-math-mode .mq-int > .mq-supsub {
+    font-size: 80%;
+    vertical-align: -1.1em;
+    padding-right: 0.2em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sup > .mq-sup-inner {
+    vertical-align: 1.3em;
+}
+.mq-math-mode .mq-int > .mq-supsub > .mq-sub {
+    margin-left: -0.35em;
+}
+.mq-math-mode .mq-roman {
+    font-style: normal;
+}
+.mq-math-mode .mq-sans-serif {
+    font-family: sans-serif, Symbola, serif;
+}
+.mq-math-mode .mq-monospace {
+    font-family: monospace, Symbola, serif;
+}
+.mq-math-mode .mq-overline {
+    border-top: 1px solid black;
+    margin-top: 1px;
+}
+.mq-math-mode .mq-underline {
+    border-bottom: 1px solid black;
+    margin-bottom: 1px;
+}
+.mq-math-mode .mq-binary-operator {
+    padding: 0 0.2em;
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-math-mode .mq-supsub {
+    text-align: left;
+    font-size: 90%;
+    vertical-align: -0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only {
+    vertical-align: 0.5em;
+}
+.mq-math-mode .mq-supsub.mq-sup-only .mq-sup {
+    display: inline-block;
+    vertical-align: text-bottom;
+}
+.mq-math-mode .mq-supsub .mq-sup {
+    display: block;
+}
+.mq-math-mode .mq-supsub .mq-sub {
+    display: block;
+    float: left;
+}
+.mq-math-mode .mq-supsub .mq-binary-operator {
+    padding: 0 0.1em;
+}
+.mq-math-mode .mq-supsub .mq-fraction {
+    font-size: 70%;
+}
+.mq-math-mode sup.mq-nthroot {
+    font-size: 80%;
+    vertical-align: 0.8em;
+    margin-right: -0.6em;
+    margin-left: 0.2em;
+    min-width: 0.5em;
+}
+.mq-math-mode .mq-paren {
+    padding: 0 0.1em;
+    vertical-align: top;
+    -webkit-transform-origin: center 0.06em;
+    -moz-transform-origin: center 0.06em;
+    -ms-transform-origin: center 0.06em;
+    -o-transform-origin: center 0.06em;
+    transform-origin: center 0.06em;
+}
+.mq-math-mode .mq-paren.mq-ghost {
+    color: silver;
+}
+.mq-math-mode .mq-paren + span {
+    margin-top: 0.1em;
+    margin-bottom: 0.1em;
+}
+.mq-math-mode .mq-array {
+    vertical-align: middle;
+    text-align: center;
+}
+.mq-math-mode .mq-array > span {
+    display: block;
+}
+.mq-math-mode .mq-operator-name {
+    font-family: Symbola, 'Times New Roman', serif;
+    line-height: 0.9;
+    font-style: normal;
+}
+.mq-math-mode var.mq-operator-name.mq-first {
+    padding-left: 0.2em;
+}
+.mq-math-mode var.mq-operator-name.mq-last,
+.mq-math-mode .mq-supsub.mq-after-operator-name {
+    padding-right: 0.2em;
+}
+.mq-math-mode .mq-fraction {
+    font-size: 90%;
+    text-align: center;
+    vertical-align: -0.4em;
+    padding: 0 0.2em;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link {
+    display: -moz-groupbox;
+}
+.mq-math-mode .mq-fraction,
+.mq-math-mode .mq-large-operator,
+.mq-math-mode x:-moz-any-link,
+.mq-math-mode x:default {
+    display: inline-block;
+}
+.mq-math-mode .mq-numerator,
+.mq-math-mode .mq-denominator {
+    display: block;
+}
+.mq-math-mode .mq-numerator {
+    padding: 0 0.1em;
+}
+.mq-math-mode .mq-denominator {
+    border-top: 1px solid;
+    float: right;
+    width: 100%;
+    padding: 0.1em;
+}
+.mq-math-mode .mq-sqrt-prefix {
+    padding-top: 0;
+    position: relative;
+    top: 0.1em;
+    vertical-align: top;
+    -webkit-transform-origin: top;
+    -moz-transform-origin: top;
+    -ms-transform-origin: top;
+    -o-transform-origin: top;
+    transform-origin: top;
+}
+.mq-math-mode .mq-sqrt-stem {
+    border-top: 1px solid;
+    margin-top: 1px;
+    padding-left: 0.15em;
+    padding-right: 0.2em;
+    margin-right: 0.1em;
+    padding-top: 1px;
+}
+.mq-math-mode .mq-vector-prefix {
+    display: block;
+    text-align: center;
+    line-height: 0.25em;
+    margin-bottom: -0.1em;
+    font-size: 0.75em;
+}
+.mq-math-mode .mq-vector-stem {
+    display: block;
+}
+.mq-math-mode .mq-large-operator {
+    vertical-align: -0.2em;
+    padding: 0.2em;
+    text-align: center;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator big,
+.mq-math-mode .mq-large-operator .mq-to {
+    display: block;
+}
+.mq-math-mode .mq-large-operator .mq-from,
+.mq-math-mode .mq-large-operator .mq-to {
+    font-size: 80%;
+}
+.mq-math-mode .mq-large-operator .mq-from {
+    float: right;
+    /* take out of normal flow to manipulate baseline */
+    width: 100%;
+}
+.mq-math-mode,
+.mq-math-mode .mq-editable-field {
+    cursor: text;
+    font-family: Symbola, 'Times New Roman', serif;
+}
+.mq-math-mode .mq-overarrow {
+    border-top: 1px solid black;
+    margin-top: 1px;
+    padding-top: 0.2em;
+}
+.mq-math-mode .mq-overarrow:before {
+    display: block;
+    position: relative;
+    top: -0.34em;
+    font-size: 0.5em;
+    line-height: 0em;
+    content: '\27A4';
+    text-align: right;
+}
+.mq-math-mode .mq-overarrow.mq-arrow-left:before {
+    -moz-transform: scaleX(-1);
+    -o-transform: scaleX(-1);
+    -webkit-transform: scaleX(-1);
+    transform: scaleX(-1);
+    filter: FlipH;
+    -ms-filter: 'FlipH';
+}
+.mq-math-mode .mq-selection,
+.mq-editable-field .mq-selection,
+.mq-math-mode .mq-selection .mq-non-leaf,
+.mq-editable-field .mq-selection .mq-non-leaf,
+.mq-math-mode .mq-selection .mq-scaled,
+.mq-editable-field .mq-selection .mq-scaled {
+    background: #b4d5fe !important;
+    background: Highlight !important;
+    color: HighlightText;
+    border-color: HighlightText;
+}
+.mq-math-mode .mq-selection .mq-matrixed,
+.mq-editable-field .mq-selection .mq-matrixed {
+    background: #39f !important;
+}
+.mq-math-mode .mq-selection .mq-matrixed-container,
+.mq-editable-field .mq-selection .mq-matrixed-container {
+    filter: progid:DXImageTransform.Microsoft.Chroma(color='#3399FF') !important;
+}
+.mq-math-mode .mq-selection.mq-blur,
+.mq-editable-field .mq-selection.mq-blur,
+.mq-math-mode .mq-selection.mq-blur .mq-non-leaf,
+.mq-editable-field .mq-selection.mq-blur .mq-non-leaf,
+.mq-math-mode .mq-selection.mq-blur .mq-scaled,
+.mq-editable-field .mq-selection.mq-blur .mq-scaled,
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed {
+    background: #d4d4d4 !important;
+    color: black;
+    border-color: black;
+}
+.mq-math-mode .mq-selection.mq-blur .mq-matrixed-container,
+.mq-editable-field .mq-selection.mq-blur .mq-matrixed-container {
+    filter: progid:DXImageTransform.Microsoft.Chroma(color='#D4D4D4') !important;
+}
+.mq-editable-field .mq-textarea,
+.mq-math-mode .mq-textarea {
+    position: relative;
+    -webkit-user-select: text;
+    -moz-user-select: text;
+    user-select: text;
+}
+.mq-editable-field .mq-textarea *,
+.mq-math-mode .mq-textarea *,
+.mq-editable-field .mq-selectable,
+.mq-math-mode .mq-selectable {
+    -webkit-user-select: text;
+    -moz-user-select: text;
+    user-select: text;
+    position: absolute;
+    clip: rect(1em 1em 1em 1em);
+    -webkit-transform: scale(0);
+    -moz-transform: scale(0);
+    -ms-transform: scale(0);
+    -o-transform: scale(0);
+    transform: scale(0);
+    resize: none;
+    width: 1px;
+    height: 1px;
+}
+.mq-math-mode .mq-matrixed {
+    background: white;
+    display: -moz-inline-box;
+    display: inline-block;
+}
+.mq-math-mode .mq-matrixed-container {
+    filter: progid:DXImageTransform.Microsoft.Chroma(color='white');
+    margin-top: -0.1em;
+}
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index bde6c1315..07c70af77 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -3,8 +3,7 @@ import React, { Component, createRef } from 'react';
 // Import JQuery, required for the functioning of the equation editor
 import $ from 'jquery';
 
-// Import the styles from the Mathquill editor
-import 'mathquill/build/mathquill.css';
+import './EquationEditor.scss';
 
 // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
 // @ts-ignore
@@ -14,6 +13,8 @@ window.jQuery = $;
 // @ts-ignore
 require('mathquill/build/mathquill');
 
+(window as any).MathQuill = (window as any).MathQuill.getInterface(1);
+
 type EquationEditorProps = {
     onChange(latex: string): void;
     value: string;
@@ -74,8 +75,7 @@ class EquationEditor extends Component<EquationEditorProps> {
             autoOperatorNames,
         };
 
-        // @ts-ignore
-        this.mathField = (MathQuill as any).MathField(this.element.current, config);
+        this.mathField = (window as any).MathQuill.MathField(this.element.current, config);
         this.mathField.latex(value || '');
     }
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 4f8e8769a..244de7849 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { isEqual } from 'lodash';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
 import { history } from 'prosemirror-history';
@@ -24,7 +24,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
@@ -129,7 +129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
 
     @computed get noSidebar() {
-        return this.props.docViewPath().lastElement()?.props.hideDecorationTitle || this.props.noSidebar || this.Document._layout_noSidebar;
+        return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
     }
     @computed get layout_sidebarWidthPercent() {
         return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -138,7 +138,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get layout_autoHeight() {
-        return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight;
+        return (this._props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this._props.ignoreAutoHeight;
     }
     @computed get textHeight() {
         return NumCast(this.dataDoc[this.fieldKey + '_height']);
@@ -150,7 +150,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.SidebarKey + '_height']);
     }
     @computed get titleHeight() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0;
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
     }
     @computed get layout_autoHeightMargins() {
         return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
@@ -162,7 +162,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
     }
     @computed get config() {
-        this._keymap = buildKeymap(schema, this.props);
+        this._keymap = buildKeymap(schema, this._props);
         this._rules = new RichTextRules(this.Document, this);
         return {
             schema,
@@ -208,12 +208,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    constructor(props: any) {
+    _prevProps: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>;
+    @override _props: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>;
+    constructor(props: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         FormattedTextBox.Instance = this;
         this._recordingStart = Date.now();
     }
 
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     // removes all hyperlink anchors for the removed linkDoc
     // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one.
     // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing.
@@ -315,10 +323,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 return target;
             };
 
-            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
         });
         const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
-        this.props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
+        this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
         let ele: Opt<HTMLDivElement> = undefined;
         try {
             const contents = window.getSelection()?.getRangeAt(0).cloneContents();
@@ -361,7 +369,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
                     if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
                         // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
-                        if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+                        if ((this._finishingLink || this._props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
                             const numstring = NumCast(dataDoc[this.fieldKey], null);
                             dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
                             dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
@@ -390,7 +398,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     this._editorView.updateState(EditorState.fromJSON(this.config, json));
                 }
             }
-            if (window.getSelection()?.isCollapsed && this.props.rootSelected?.()) {
+            if (window.getSelection()?.isCollapsed && this._props.rootSelected?.()) {
                 AnchorMenu.Instance.fadeOut(true);
             }
         }
@@ -438,7 +446,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     autoLink = () => {
         const newAutoLinks = new Set<Doc>();
-        const oldAutoLinks = LinkManager.Links(this.props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+        const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
         if (this._editorView?.state.doc.textContent) {
             const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
             const f = this._editorView.state.selection.from;
@@ -456,7 +464,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     updateTitle = () => {
         const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
         if (
-            !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+            !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
             (title.startsWith('-') || title.startsWith('@')) &&
             this._editorView &&
             !this.dataDoc.title_custom &&
@@ -499,7 +507,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                                     DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
                             newAutoLinks.add(alink);
                             // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
-                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+                            const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
                             allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
                             const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
                             tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -568,7 +576,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     @undoBatch
-    @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.annoDragData) {
             de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true);
@@ -584,7 +591,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
                     const fieldKey = Doc.LayoutFieldKey(draggedDoc);
-                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+                    if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
                         Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
                     }
 
@@ -742,9 +749,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
     };
     sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => {
-        const localDelta = this.props
+        const localDelta = this._props
             .ScreenToLocalTransform()
-            .scale(this.props.NativeDimScaling?.() || 1)
+            .scale(this._props.NativeDimScaling?.() || 1)
             .transformDirection(delta[0], delta[1]);
         const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100;
         const width = NumCast(this.layoutDoc._width) + localDelta[0];
@@ -758,15 +765,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     deleteAnnotation = (anchor: Doc) => {
         const batch = UndoManager.StartBatch('delete link');
         LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]);
-        // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]);
-        // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
+        // const docAnnotations = DocListCast(this._props.dataDoc[this.fieldKey]);
+        // this._props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
         // AnchorMenu.Instance.fadeOut(true);
-        this.props.select(false);
+        this._props.select(false);
         setTimeout(batch.end); // wait for reaction to remove link from document
     };
 
     @undoBatch
-    pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {});
+    pinToPres = (anchor: Doc) => this._props.pinToPres(anchor, {});
 
     @undoBatch
     makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle);
@@ -776,7 +783,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const trail = DocCast(anchor.presentationTrail);
         if (trail) {
             Doc.ActivePresentation = trail;
-            this.props.addDocTab(trail, OpenWhere.replaceRight);
+            this._props.addDocTab(trail, OpenWhere.replaceRight);
         }
     };
 
@@ -913,7 +920,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
         optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
-        this.props.renderDepth &&
+        this._props.renderDepth &&
             optionItems.push({
                 description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
                 event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
@@ -958,7 +965,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     generateImage = async () => {
         GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance?.setImgTargetDoc(this.Document);
-        GPTPopup.Instance.addToCollection = this.props.addDocument;
+        GPTPopup.Instance.addToCollection = this._props.addDocument;
         GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
         GPTPopup.Instance.generateImage();
     };
@@ -1127,7 +1134,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000));
                 return focusSpeed;
             } else {
-                return this.props.focus(this.Document, options);
+                return this._props.focus(this.Document, options);
             }
         }
     };
@@ -1141,10 +1148,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     @computed get contentScaling() {
-        return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1;
+        return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
     }
     componentDidMount() {
-        !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+        !this._props.dontSelectOnLoad && this._props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
         this._cachedLinks = LinkManager.Links(this.Document);
         this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
         this._disposers.layout_autoHeight = reaction(
@@ -1157,7 +1164,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             { fireImmediately: true }
         );
         this._disposers.width = reaction(
-            () => this.props.PanelWidth(),
+            () => this._props.PanelWidth(),
             width => this.tryUpdateScrollHeight()
         );
         this._disposers.scrollHeight = reaction(
@@ -1171,13 +1178,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => {
                 const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
                 if (
-                    (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this.props.isSelected()) && //
+                    (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
                     layout_autoHeight &&
                     newHeight &&
                     newHeight !== this.layoutDoc.height &&
-                    !this.props.dontRegisterView
+                    !this._props.dontRegisterView
                 ) {
-                    this.props.setHeight?.(newHeight);
+                    this._props.setHeight?.(newHeight);
                 }
             },
             { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') }
@@ -1219,7 +1226,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         );
         this._disposers.pullDoc = reaction(
-            () => this.props.Document[Pulls],
+            () => this._props.Document[Pulls],
             () => {
                 if (!DocumentButtonBar.hasPulledHack) {
                     DocumentButtonBar.hasPulledHack = true;
@@ -1228,7 +1235,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         );
         this._disposers.pushDoc = reaction(
-            () => this.props.Document[Pushes],
+            () => this._props.Document[Pushes],
             () => {
                 if (!DocumentButtonBar.hasPushedHack) {
                     DocumentButtonBar.hasPushedHack = true;
@@ -1244,7 +1251,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
 
         this._disposers.selected = reaction(
-            () => this.props.rootSelected?.(),
+            () => this._props.rootSelected?.(),
             action(selected => {
                 //selected && setTimeout(() => this.prepareForTyping());
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
@@ -1254,14 +1261,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
                 }
                 if (this._editorView && selected) {
-                    RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
+                    RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
                     setTimeout(this.autoLink, 20);
                 }
             }),
             { fireImmediately: true }
         );
 
-        if (!this.props.dontRegisterView) {
+        if (!this._props.dontRegisterView) {
             this._disposers.record = reaction(
                 () => this._recordingDictation,
                 () => {
@@ -1276,7 +1283,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._disposers.scroll = reaction(
             () => NumCast(this.layoutDoc._layout_scrollTop),
             pos => {
-                if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) {
+                if (!this._ignoreScroll && this._scrollRef.current && !this._props.dontSelectOnLoad) {
                     const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
@@ -1439,8 +1446,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const self = this;
         return new Plugin({
             view(newView) {
-                runInAction(() => self.props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
-                return new RichTextMenuPlugin({ editorProps: this.props });
+                runInAction(() => self._props.rootSelected?.() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+                return new RichTextMenuPlugin({ editorProps: this._props });
             },
         });
     }
@@ -1462,7 +1469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
-                        const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale;
+                        const scrollPos = scrollRef.scrollTop + shift * self._props.ScreenToLocalTransform().Scale;
                         if (this._focusSpeed !== undefined) {
                             scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
                         } else {
@@ -1514,11 +1521,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = Doc.AreProtosEqual(this.props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
-        if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
+        const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+        if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
             const selLoadChar = FormattedTextBox.SelectOnLoadChar;
             FormattedTextBox.SelectOnLoad = undefined;
-            this.props.select(false);
+            this._props.select(false);
             if (selLoadChar) {
                 const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined;
                 const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) });
@@ -1534,7 +1541,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         }
         selectOnLoad && this._editorView!.focus();
-        if (this.props.isContentActive()) this.prepareForTyping();
+        if (this._props.isContentActive()) this.prepareForTyping();
         if (this._editorView) {
             const tr = this._editorView.state.tr;
             const { from, to } = tr.selection;
@@ -1557,15 +1564,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []),
             ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []),
             ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []),
-            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily) })] : []),
-            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize) })] : []),
+            ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []),
+            ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []),
             ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []),
             ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })],
         ];
         this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks));
     };
 
-    @action
     componentWillUnmount() {
         if (this._recordingDictation) {
             this._recordingDictation = !this._recordingDictation;
@@ -1598,7 +1604,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     const func = () => {
                         const docView = DocumentManager.Instance.getDocumentView(audiodoc);
                         if (!docView) {
-                            this.props.addDocTab(audiodoc, OpenWhere.addBottom);
+                            this._props.addDocTab(audiodoc, OpenWhere.addBottom);
                             setTimeout(func);
                         } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
                     };
@@ -1614,7 +1620,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._downTime = Date.now();
         this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
         FormattedTextBoxComment.textBox = this;
-        if (e.button === 0 && this.props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
                 // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
                 e.stopPropagation(); // if the text box's content is active, then it consumes all down events
@@ -1636,7 +1642,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
         if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
-        else if (this.props.isContentActive(true)) {
+        else if (this._props.isContentActive(true)) {
             const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
             let xpos = pcords?.pos || 0;
             while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1651,7 +1657,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @action
     onDoubleClick = (e: React.MouseEvent): void => {
         FormattedTextBoxComment.textBox = this;
-        if (e.button === 0 && this.props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
             if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
                 // stop propagation if not in sidebar
                 e.stopPropagation(); // if the text box is selected, then it consumes all click events
@@ -1662,7 +1668,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         FormattedTextBoxComment.Hide();
 
-        if (e.buttons === 1 && this.props.rootSelected?.() && !e.altKey) {
+        if (e.buttons === 1 && this._props.rootSelected?.() && !e.altKey) {
             e.stopPropagation();
         }
     };
@@ -1673,14 +1679,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
     @action
     onFocused = (e: React.FocusEvent): void => {
-        console.log('FOCUSED = ' + this.layoutDoc.title + ' ' + this.props.rootSelected?.());
         //applyDevTools.applyDevTools(this._editorView);
-        this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
+        this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this._props, this.layoutDoc);
         e.stopPropagation();
     };
 
     onClick = (e: React.MouseEvent): void => {
-        if (!this.props.isContentActive()) return;
+        if (!this._props.isContentActive()) return;
         if ((e.nativeEvent as any).handledByInnerReactInstance) {
             e.stopPropagation();
             return;
@@ -1709,7 +1714,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
             }
         }
-        if (this.props.rootSelected?.()) {
+        if (this._props.rootSelected?.()) {
             // if text box is selected, then it consumes all click events
             (e.nativeEvent as any).handledByInnerReactInstance = true;
             this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey);
@@ -1723,7 +1728,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
         const clickPos = this._editorView!.posAtCoords({ left: x, top: y });
         let olistPos = clickPos?.pos;
-        if (clickPos && olistPos && this.props.rootSelected?.()) {
+        if (clickPos && olistPos && this._props.rootSelected?.()) {
             const clickNode = this._editorView?.state.doc.nodeAt(olistPos);
             const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1));
             olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos;
@@ -1772,7 +1777,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 tr && this._editorView.dispatch(tr);
             }
         }
-        if (RichTextMenu.Instance?.view === this._editorView && !this.props.rootSelected?.()) {
+        if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) {
             RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
         }
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
@@ -1797,7 +1802,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if ((e.altKey || e.ctrlKey) && e.key === 't') {
             e.preventDefault();
             e.stopPropagation();
-            this.props.setTitleFocus?.();
+            this._props.setTitleFocus?.();
             return;
         }
         const state = this._editorView!.state;
@@ -1851,7 +1856,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
     onScroll = (e: React.UIEvent) => {
         if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
-            if (!this.props.dontSelectOnLoad) {
+            if (!this._props.dontSelectOnLoad) {
                 this._ignoreScroll = true;
                 this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
                 this._ignoreScroll = false;
@@ -1861,7 +1866,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     tryUpdateScrollHeight = () => {
-        const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
+        const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
         const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
         if (children && !SnappingManager.GetIsDragging()) {
             const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
@@ -1871,7 +1876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             };
             const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
             const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
-            if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) {
+            if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
                 // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
                 const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
 
@@ -1883,8 +1888,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         }
     };
-    fitContentsToBox = () => BoolCast(this.props.Document._freeform_fitContentsToBox);
-    sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+    fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+    sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
         return this.addDocument(doc, sidebarKey);
@@ -1892,12 +1897,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
     sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
     setSidebarHeight = (height: number) => (this.dataDoc[this.SidebarKey + '_height'] = height);
-    sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+    sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
     sidebarScreenToLocal = () =>
-        this.props
+        this._props
             .ScreenToLocalTransform()
-            .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0)
-            .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
+            .translate(-(this._props.PanelWidth() - this.sidebarWidth()) / (this._props.NativeDimScaling?.() || 1), 0)
+            .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.NativeDimScaling?.() || 1));
 
     @computed get audioHandle() {
         return !this._recordingDictation ? null : (
@@ -1920,9 +1925,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         TraceMobx();
         const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length;
         const color = !annotated ? Colors.WHITE : Colors.BLACK;
-        const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
+        const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.WidgetColor + (annotated ? ':annotated' : ''));
 
-        return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
+        return !annotated && (!this._props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : (
             <div
                 className="formattedTextBox-sidebar-handle"
                 onPointerDown={this.sidebarDown}
@@ -1941,7 +1946,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             return ComponentTag === CollectionStackingView ? (
                 <SidebarAnnos
                     ref={this._sidebarRef}
-                    {...this.props}
+                    {...this._props}
                     Document={this.Document}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
@@ -1958,14 +1963,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     setHeight={this.setSidebarHeight}
                 />
             ) : (
-                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
                     <ComponentTag
-                        {...this.props}
+                        {...this._props}
                         ref={this._sidebarTagRef as any}
                         setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
-                        PanelHeight={this.props.PanelHeight}
+                        PanelHeight={this._props.PanelHeight}
                         PanelWidth={this.sidebarWidth}
                         xPadding={0}
                         yPadding={0}
@@ -1979,7 +1984,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         moveDocument={this.sidebarMoveDocument}
                         addDocument={this.sidebarAddDocument}
                         ScreenToLocalTransform={this.sidebarScreenToLocal}
-                        renderDepth={this.props.renderDepth + 1}
+                        renderDepth={this._props.renderDepth + 1}
                         setHeight={this.setSidebarHeight}
                         fitContentsToBox={this.fitContentsToBox}
                         noSidebar={true}
@@ -1997,12 +2002,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     }
     cycleAlternateText = () => {
         if (this.layoutDoc._layout_enableAltContentUI) {
-            const usePath = this.layoutDoc[`_${this.props.fieldKey}_usePath`];
-            this.layoutDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+            const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
+            this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
         }
     };
     @computed get overlayAlternateIcon() {
-        const usePath = this.layoutDoc[`_${this.props.fieldKey}_usePath`];
+        const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`];
         return (
             <Tooltip
                 title={
@@ -2024,7 +2029,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     className="formattedTextBox-alternateButton"
                     onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => this.cycleAlternateText())}
                     style={{
-                        display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
+                        display: this._props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
                         background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
                         color: usePath === undefined ? 'black' : 'white',
                     }}>
@@ -2033,9 +2038,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             </Tooltip>
         );
     }
-    @computed get fieldKey() {
-        const usePath = StrCast(this.layoutDoc[`${this.props.fieldKey}_usePath`]);
-        return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this.props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
+    get fieldKey() {
+        const usePath = StrCast(this.layoutDoc[`${this._props.fieldKey}_usePath`]);
+        return this._props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this._props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : '');
     }
     @observable _isHovering = false;
     onPassiveWheel = (e: WheelEvent) => {
@@ -2049,14 +2054,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
 
         // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
-        if (this.props.isContentActive()) {
-            const scale = this.props.NativeDimScaling?.() || 1;
-            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        if (this._props.isContentActive()) {
+            const scale = this._props.NativeDimScaling?.() || 1;
+            const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
             const height = Number(styleFromLayoutString.height?.replace('px', ''));
             // prevent default if selected || child is active  but this doc isn't scrollable
             if (
-                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this.props.PanelHeight()) / scale) && //
-                (this.props.rootSelected?.() || this.isAnyChildContentActive())
+                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this._props.PanelHeight()) / scale) && //
+                (this._props.rootSelected?.() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
             }
@@ -2065,25 +2070,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
     _oldWheel: any;
     @computed get fontColor() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
     }
     @computed get fontSize() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
     }
     @computed get fontFamily() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
     }
     @computed get fontWeight() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight);
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontWeight);
     }
     render() {
         TraceMobx();
-        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+        const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
         const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
-        setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
-        const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0);
-        const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0);
-        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
+        const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
+        const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
+        const styleFromLayoutString = Doc.styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
         return styleFromLayoutString?.height === '0px' ? null : (
             <div
                 className="formattedTextBox"
@@ -2095,7 +2100,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
                 }}
                 style={{
-                    ...(this.props.dontScale
+                    ...(this._props.dontScale
                         ? {}
                         : {
                               transform: `scale(${scale})`,
@@ -2114,9 +2119,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     className="formattedTextBox-cont"
                     ref={this._ref}
                     style={{
-                        cursor: this.props.isContentActive() ? 'text' : undefined,
-                        height: this.props.height ? 'max-content' : undefined,
-                        pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
+                        cursor: this._props.isContentActive() ? 'text' : undefined,
+                        height: this._props.height ? 'max-content' : undefined,
+                        pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
                     onKeyDown={this.onKeyDown}
@@ -2131,7 +2136,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         className="formattedTextBox-outer"
                         ref={this._scrollRef}
                         style={{
-                            width: this.props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
+                            width: this._props.dontSelectOnLoad || this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`,
                             overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined,
                         }}
                         onScroll={this.onScroll}
@@ -2148,8 +2153,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                             }}
                         />
                     </div>
-                    {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
-                    {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
+                    {this.noSidebar || this._props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
+                    {this.noSidebar || this.Document._layout_noSidebar || this._props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle}
                     {this.audioHandle}
                     {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
                 </div>
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 7ce06cf7f..d7e799161 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { lift, wrapIn } from 'prosemirror-commands';
 import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
@@ -10,7 +10,7 @@ import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { numberRange } from '../../../../Utils';
+import { copyProps, numberRange } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
@@ -63,8 +63,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private showLinkDropdown: boolean = false;
 
     _reaction: IReactionDisposer | undefined;
-    constructor(props: Readonly<{}>) {
+    _prevProps: AntimodeMenuProps;
+    @override _props: AntimodeMenuProps;
+    constructor(props: AntimodeMenuProps) {
         super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
         runInAction(() => {
             RichTextMenu.Instance = this;
             this.updateMenu(undefined, undefined, props, this.layoutDoc);
@@ -107,6 +111,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return BoolCast(this.layoutDoc?.layout_centered);
     }
     _disposer: IReactionDisposer | undefined;
+    componentDidUpdate() {
+        copyProps(this);
+    }
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
@@ -361,7 +368,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
             SelectionManager.Views().forEach(dv => (dv.Document._text_fontSize = fontSize));
         } else Doc.UserDoc().fontSize = fontSize;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
     };
 
     setFontFamily = (family: string) => {
@@ -370,7 +377,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
             this.view.focus();
         } else Doc.UserDoc().fontFamily = family;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
     };
 
     setHighlight(color: string) {
@@ -379,7 +386,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true);
             this.view.focus();
         } else Doc.UserDoc()._fontHighlight = color;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
     }
 
     setColor(color: string) {
@@ -388,7 +395,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
             this.view.focus();
         } else Doc.UserDoc().fontColor = color;
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
     }
 
     // TODO: remove doesn't work
@@ -429,7 +436,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             }
         }
         this.view.focus();
-        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
+        this.updateMenu(this.view, undefined, this._props, this.layoutDoc);
     };
 
     insertSummarizer(state: EditorState, dispatch: any) {
@@ -669,7 +676,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     @undoBatch
-    @action
     deleteLink = () => {
         if (this.view) {
             const linkAnchor = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor);
@@ -816,6 +822,18 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
     @observable private showDropdown: boolean = false;
     private ref: HTMLDivElement | null = null;
 
+    _prevProps: React.PropsWithChildren<ButtonDropdownProps>;
+    @observable _props: React.PropsWithChildren<ButtonDropdownProps>;
+    constructor(props: React.PropsWithChildren<ButtonDropdownProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     componentDidMount() {
         document.addEventListener('pointerdown', this.onBlur);
     }
@@ -850,22 +868,22 @@ export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
     render() {
         return (
             <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}>
-                {!this.props.pdf ? (
-                    <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
-                        {this.props.button}
-                        <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this.props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+                {!this._props.pdf ? (
+                    <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
+                        {this._props.button}
+                        <div style={{ marginTop: '-8.5', position: 'relative' }} onPointerDown={!this._props.openDropdownOnButton ? this.onDropdownClick : undefined}>
                             <FontAwesomeIcon icon="caret-down" size="sm" />
                         </div>
                     </div>
                 ) : (
                     <>
-                        {this.props.button}
+                        {this._props.button}
                         <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}>
                             <FontAwesomeIcon icon="caret-down" size="sm" />
                         </button>
                     </>
                 )}
-                {this.showDropdown ? this.props.dropdownContent : null}
+                {this.showDropdown ? this._props.dropdownContent : null}
             </div>
         );
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 1317ca814..a224ec7fa 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,9 +36,6 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import _ = require('lodash');
-const { Howl } = require('howler');
-
 export interface pinDataTypes {
     scrollable?: boolean;
     dataviz?: number[];
@@ -156,7 +153,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
     removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
 
-    @action
     componentWillUnmount() {
         this._unmounting = true;
         if (this._presTimer) clearTimeout(this._presTimer);
@@ -187,7 +183,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         this._unmounting = false;
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
@@ -603,7 +599,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 const dv = DocumentManager.Instance.getDocumentView(bestTarget);
                 if (dv) {
                     changed = true;
-                    const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
+                    const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height);
                     activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
                     dv.ComponentView?.brushView?.(viewport, transTime, 2500);
                 }
@@ -748,7 +744,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragViewCache = Array.from(this._dragArray);
         const eleViewCache = Array.from(this._eleArray);
         const resetSelection = action(() => {
-            if (!this.props.isSelected()) {
+            if (!this._props.isSelected()) {
                 const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
                 if (presDocView) SelectionManager.SelectView(presDocView, false);
                 this.clearSelectedArray();
@@ -982,9 +978,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     enterMinimize = () => {
         this.updateCurrentPresentation(this.Document);
         clearTimeout(this._presTimer);
-        const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-        this.props.removeDocument?.(this.layoutDoc);
-        return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
+        const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        this._props.removeDocument?.(this.layoutDoc);
+        return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
     };
     exitMinimize = () => {
         if (Doc.IsInMyOverlay(this.layoutDoc)) {
@@ -1052,7 +1048,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return StrCast(activeItem.presentation_movement);
     });
 
-    whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
+    whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
     // For dragging documents into the presentation trail
     addDocumentFilter = (docs: Doc[]) => {
         docs.forEach((doc, i) => {
@@ -1063,7 +1059,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
                     audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
                     audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
-                    this.props.pinToPres(audio, { audioRange: true });
+                    this._props.pinToPres(audio, { audioRange: true });
                     setTimeout(() => this.removeDocument(doc), 0);
                     return false;
                 }
@@ -1079,8 +1075,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
-    getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
-    panelHeight = () => this.props.PanelHeight() - 40;
+    getTransform = () => this._props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
+    panelHeight = () => this._props.PanelHeight() - 40;
     /**
      * For sorting the array so that the order is maintained when it is dropped.
      */
@@ -1313,7 +1309,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             } else if (doc.config_pinView && presCollection === tagDoc && dv) {
                 // Case B: Document is presPinView and is presCollection
                 const scale = 1 / NumCast(doc.config_viewScale);
-                const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv.props.PanelWidth(), dv.props.PanelHeight()]);
+                const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv._props.PanelWidth(), dv._props.PanelHeight()]);
                 const height = (viewBounds[3] - viewBounds[1]) * scale;
                 const width = (viewBounds[2] - viewBounds[0]) * scale;
                 const indWidth = width / 10;
@@ -1433,46 +1429,39 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));
 
     @undoBatch
-    @action
     updateHideBefore = (activeItem: Doc) => {
         activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
         this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
     };
 
     @undoBatch
-    @action
     updateHide = (activeItem: Doc) => {
         activeItem.presentation_hide = !activeItem.presentation_hide;
         this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
     };
 
     @undoBatch
-    @action
     updateHideAfter = (activeItem: Doc) => {
         activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
         this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
     };
 
     @undoBatch
-    @action
     updateOpenDoc = (activeItem: Doc) => {
         activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
         this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
     };
 
     @undoBatch
-    @action
     updateEaseFunc = (activeItem: Doc) => {
         activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
         this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
     };
 
     @undoBatch
-    @action
     updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect));
 
     @undoBatch
-    @action
     updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));
 
     static _sliderBatch: any;
@@ -1505,7 +1494,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @undoBatch
-    @action
     applyTo = (array: Doc[]) => {
         this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
         this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
@@ -2219,10 +2207,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const config_data = Cast(this.Document.data, listSpec(Doc));
             if (data && config_data) {
                 data.push(doc);
-                this.props.pinToPres(doc, {});
+                this._props.pinToPres(doc, {});
                 this.gotoDocument(this.childDocs.length, this.activeItem);
             } else {
-                this.props.addDocTab(doc, OpenWhere.addRight);
+                this._props.addDocTab(doc, OpenWhere.addRight);
             }
         }
     };
@@ -2283,15 +2271,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed
     get toolbarWidth(): number {
-        return this.props.PanelWidth();
+        return this._props.PanelWidth();
     }
 
     @action
-    toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250);
+    toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);
 
     @computed get toolbar() {
-        const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
-        const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
+        const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
+        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
         const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
         const activeColor = SettingsManager.userVariantColor;
@@ -2320,7 +2308,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </Tooltip>
                         <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
                             <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
-                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.propertiesWidth > 0 ? activeColor : inactiveColor }} />
+                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
                             </div>
                         </Tooltip>
                     </>
@@ -2366,7 +2354,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 }
                             })}>
                             <FontAwesomeIcon icon={'play-circle'} />
-                            <div style={{ display: this.props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
+                            <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
                         </div>
                         {mode === CollectionViewType.Carousel3D || isMini ? null : (
                             <div
@@ -2476,12 +2464,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <b>1</b>
                     </div>
                 </Tooltip>
-                <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
+                <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this._props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
                     {inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
                     {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
                 </div>
                 <div className="presPanel-divider"></div>
-                {this.props.PanelWidth() > 250 ? (
+                {this._props.PanelWidth() > 250 ? (
                     <div
                         className="presPanel-button-text"
                         onClick={undoBatch(
@@ -2527,7 +2515,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @undoBatch
-    @action
     exitClicked = () => {
         this.layoutDoc.presentation_status = this._exitTrail?.() ?? this.exitMinimize();
         clearTimeout(this._presTimer);
@@ -2570,7 +2557,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             this.itemIndex === this.childDocs.length - 1 &&
             (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
         const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
-        return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
+        return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
             <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
                 <div
                     className="presPanelOverlay"
@@ -2620,8 +2607,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div className="Slide">
                         {mode !== CollectionViewType.Invalid ? (
                             <CollectionView
-                                {...this.props}
-                                PanelWidth={this.props.PanelWidth}
+                                {...this._props}
+                                PanelWidth={this._props.PanelWidth}
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index cd6beac57..9fcb496b8 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,12 +1,12 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -43,9 +43,21 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @observable _dragging = false;
 
+    _prevProps: FieldViewProps;
+    @override _props: FieldViewProps;
+    constructor(props: FieldViewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     // the presentation view that renders this slide
     @computed get presBoxView() {
-        return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
+        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
     }
 
     // the presentation view document that renders this slide
@@ -56,7 +68,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Since this node is being rendered with a template, this method retrieves
     // the actual slide being rendered from the auto-generated rendering template
     @computed get slideDoc() {
-        return this.props.TemplateDataDocument ?? this.props.Document;
+        return this._props.TemplateDataDocument ?? this.Document;
     }
 
     // this is the document in the workspaces that is targeted by the slide
@@ -91,9 +103,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
-    embedWidth = () => this.props.PanelWidth() / 2;
+    embedWidth = () => this._props.PanelWidth() / 2;
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
-        return property === StyleProp.Opacity ? 1 : this.props.styleProvider?.(doc, props, property);
+        return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
     };
     /**
      * The function that is responsible for rendering a preview or not for this
@@ -106,19 +118,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     Document={PresBox.targetRenderedDoc(this.slideDoc)}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
-                    isContentActive={this.props.isContentActive}
+                    isContentActive={this._props.isContentActive}
                     styleProvider={this.styleProvider}
                     hideLinkButton={true}
                     ScreenToLocalTransform={Transform.Identity}
-                    renderDepth={this.props.renderDepth + 1}
+                    renderDepth={this._props.renderDepth + 1}
                     docViewPath={returnEmptyDoclist}
-                    childFilters={this.props.childFilters}
-                    childFiltersByRanges={this.props.childFiltersByRanges}
-                    searchFilterDocs={this.props.searchFilterDocs}
+                    childFilters={this._props.childFilters}
+                    childFiltersByRanges={this._props.childFiltersByRanges}
+                    searchFilterDocs={this._props.searchFilterDocs}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     fitContentsToBox={returnTrue}
-                    moveDocument={this.props.moveDocument!}
+                    moveDocument={this._props.moveDocument!}
                     focus={emptyFunction}
                     whenChildContentsActiveChanged={returnFalse}
                     addDocTab={returnFalse}
@@ -191,8 +203,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragArray = this.presBoxView?._dragArray ?? [];
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
-        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
-        dragData.moveDocument = this.props.moveDocument;
+        dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+        dragData.moveDocument = this._props.moveDocument;
         const dragItem: HTMLElement[] = [];
         const classesToRestore = new Map<HTMLElement, string>();
         if (dragArray.length === 1) {
@@ -265,8 +277,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     toggleProperties = () => {
-        if (SettingsManager.propertiesWidth < 5) {
-            SettingsManager.propertiesWidth = 250;
+        if (SettingsManager.Instance.propertiesWidth < 5) {
+            SettingsManager.Instance.propertiesWidth = 250;
         }
     };
 
@@ -276,7 +288,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
                 this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1;
             }
-            this.props.removeDocument?.(this.slideDoc);
+            this._props.removeDocument?.(this.slideDoc);
             this.presBoxView?.removeFromSelectedArray(this.slideDoc);
             this.removeAllRecordingInOverlay();
         }),
@@ -299,7 +311,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * @param activeItem
      */
     @undoBatch
-    @action
     updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => {
         const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc;
         activeItem.config_x = NumCast(targetDoc.x);
@@ -389,7 +400,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     );
 
     @undoBatch
-    @action
     lfg = (e: React.MouseEvent) => {
         e.stopPropagation();
         // TODO: fix this bug
@@ -404,7 +414,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     get toolbarWidth(): number {
         const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox);
         const width = NumCast(this.presBox?._width);
-        return presBoxDocView ? presBoxDocView.props.PanelWidth() : width ? width : 300;
+        return presBoxDocView ? presBoxDocView._props.PanelWidth() : width ? width : 300;
     }
 
     @computed get presButtons() {
@@ -453,8 +463,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             {!activeItem.presentation_groupWithUp
                                 ? 'Not grouped with previous slide (click to group)'
                                 : activeItem.presentation_groupWithUp === 1
-                                ? 'Run simultaneously with previous slide (click again to run after)'
-                                : 'Run after previous slide (click to ungroup from previous)'}
+                                  ? 'Run simultaneously with previous slide (click again to run after)'
+                                  : 'Run after previous slide (click to ungroup from previous)'}
                         </div>
                     }>
                     <div
@@ -530,10 +540,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
                     opacity: this._dragging ? 0.3 : 1,
-                    paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
-                    paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
-                    paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
-                    paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
+                    paddingLeft: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+                    paddingRight: NumCast(this.layoutDoc._xPadding, this._props.xPadding),
+                    paddingTop: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding, this._props.yPadding),
                 }}
                 onDoubleClick={action(e => {
                     this.toggleProperties();
@@ -552,7 +562,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         className={`presItem-slide ${isCurrent ? 'active' : ''}${this.slideDoc.runProcess ? ' testingv2' : ''}`}
                         style={{
                             display: 'infline-block',
-                            backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+                            backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                             //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
                             border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
                         }}>
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 2d8e9e2a2..3c26c307f 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,11 +1,11 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorResult } from 'react-color';
 import { Doc, Opt } from '../../../fields/Doc';
-import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
+import { copyProps, returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
 import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -23,6 +23,19 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _commentRef = React.createRef<HTMLDivElement>();
     private _cropRef = React.createRef<HTMLDivElement>();
 
+    _prevProps: AntimodeMenuProps;
+    @override _props: AntimodeMenuProps;
+    constructor(props: AntimodeMenuProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        AnchorMenu.Instance = this;
+        AnchorMenu.Instance._canFade = false;
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
 
     @observable public Status: 'marquee' | 'annotation' | '' = '';
@@ -50,13 +63,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this._left > 0;
     }
 
-    constructor(props: Readonly<{}>) {
-        super(props);
-
-        AnchorMenu.Instance = this;
-        AnchorMenu.Instance._canFade = false;
-    }
-
     componentWillUnmount() {
         this._disposer?.();
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b2373b190..62df77c0a 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
@@ -8,7 +8,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -58,6 +58,17 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable private _showWaiting = true;
     @observable private Index: number = -1;
 
+    _prevProps: IViewerProps;
+    @observable _props: IViewerProps;
+    constructor(props: IViewerProps) {
+        super(props);
+        this._prevProps = this._props = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     private _pdfViewer: any;
     private _styleRule: any; // stylesheet rule for making hyperlinks clickable
     private _retries = 0; // number of times tried to create the PDF viewer
@@ -84,7 +95,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable isAnnotating = false;
     // key where data is stored
     @computed get allAnnotations() {
-        return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges());
+        return DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this._props.childFilters(), this._props.childFiltersByRanges());
     }
     @computed get inlineTextAnnotations() {
         return this.allAnnotations.filter(a => a.text_inlineAnnotations);
@@ -96,22 +107,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
 
         this._disposers.layout_autoHeight = reaction(
-            () => this.props.layoutDoc._layout_autoHeight,
+            () => this._props.layoutDoc._layout_autoHeight,
             layout_autoHeight => {
                 if (layout_autoHeight) {
-                    this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
-                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
+                    this._props.layoutDoc._nativeHeight = NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']);
+                    this._props.setHeight?.(NumCast(this._props.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
             }
         );
 
         this._disposers.selected = reaction(
-            () => this.props.isSelected(),
+            () => this._props.isSelected(),
             selected => SelectionManager.Views().length === 1 && this.setupPdfJsViewer(),
             { fireImmediately: true }
         );
         this._disposers.curPage = reaction(
-            () => Cast(this.props.Document._layout_curPage, 'number', null),
+            () => Cast(this._props.Document._layout_curPage, 'number', null),
             page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
             { fireImmediately: true }
         );
@@ -123,7 +134,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     };
 
     copy = (e: ClipboardEvent) => {
-        if (this.props.isContentActive() && e.clipboardData) {
+        if (this._props.isContentActive() && e.clipboardData) {
             e.clipboardData.setData('text/plain', this._selectionText);
             const anchor = this._getAnchor(undefined, false);
             if (anchor) {
@@ -139,18 +150,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @action
     initialLoad = async () => {
         if (this._pageSizes.length === 0) {
-            this._pageSizes = Array<{ width: number; height: number }>(this.props.pdf.numPages);
+            this._pageSizes = Array<{ width: number; height: number }>(this._props.pdf.numPages);
             await Promise.all(
                 this._pageSizes.map((val, i) =>
-                    this.props.pdf.getPage(i + 1).then(
+                    this._props.pdf.getPage(i + 1).then(
                         action((page: Pdfjs.PDFPageProxy) => {
                             const page0or180 = page.rotate === 0 || page.rotate === 180;
                             this._pageSizes.splice(i, 1, {
                                 width: page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1],
                                 height: page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0],
                             });
-                            if (i === this.props.pdf.numPages - 1) {
-                                this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages);
+                            if (i === this._props.pdf.numPages - 1) {
+                                this._props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this._props.pdf.numPages);
                             }
                         })
                     )
@@ -167,27 +178,27 @@ export class PDFViewer extends React.Component<IViewerProps> {
     scrollFocus = (doc: Doc, scrollTop: number, options: DocFocusOptions) => {
         const mainCont = this._mainCont.current;
         let focusSpeed: Opt<number>;
-        if (doc !== this.props.Document && mainCont) {
-            const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this.props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
-            if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._layout_scrollTop) {
+        if (doc !== this._props.Document && mainCont) {
+            const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+            const scrollTo = Utils.scrollIntoView(scrollTop, doc[Height](), NumCast(this._props.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, this._scrollHeight);
+            if (scrollTo !== undefined && scrollTo !== this._props.layoutDoc._layout_scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                 else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
                 else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) });
             }
         } else {
-            this._initialScroll = { loc: NumCast(this.props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
+            this._initialScroll = { loc: NumCast(this._props.layoutDoc._layout_scrollTop), easeFunc: options.easeFunc };
         }
         return focusSpeed;
     };
-    crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
+    crop = (region: Doc | undefined, addCrop?: boolean) => this._props.crop(region, addCrop);
 
     @action
     setupPdfJsViewer = async () => {
         if (this._viewerIsSetup) return;
         this._viewerIsSetup = true;
         this._showWaiting = true;
-        this.props.setPdfViewer(this);
+        this._props.setPdfViewer(this);
         await this.initialLoad();
 
         this.createPdfViewer();
@@ -195,22 +206,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     pagesinit = () => {
         if (this._pdfViewer._setDocumentViewerElement?.offsetParent) {
-            runInAction(() => (this._pdfViewer.currentScaleValue = this.props.layoutDoc._freeform_scale = 1));
-            this.gotoPage(NumCast(this.props.Document._layout_curPage, 1));
+            runInAction(() => (this._pdfViewer.currentScaleValue = this._props.layoutDoc._freeform_scale = 1));
+            this.gotoPage(NumCast(this._props.Document._layout_curPage, 1));
         }
         document.removeEventListener('pagesinit', this.pagesinit);
         var quickScroll: { loc?: string; easeFunc?: 'ease' | 'linear' } | undefined = { loc: this._initialScroll ? this._initialScroll.loc?.toString() : '', easeFunc: this._initialScroll ? this._initialScroll.easeFunc : undefined };
         this._disposers.scale = reaction(
-            () => NumCast(this.props.layoutDoc._freeform_scale, 1),
+            () => NumCast(this._props.layoutDoc._freeform_scale, 1),
             scale => (this._pdfViewer.currentScaleValue = scale),
             { fireImmediately: true }
         );
         this._disposers.scroll = reaction(
-            () => Math.abs(NumCast(this.props.Document._layout_scrollTop)),
+            () => Math.abs(NumCast(this._props.Document._layout_scrollTop)),
             pos => {
                 if (!this._ignoreScroll) {
                     this._showWaiting && this.setupPdfJsViewer();
-                    const viewTrans = quickScroll?.loc ?? StrCast(this.props.Document._viewTransition);
+                    const viewTrans = quickScroll?.loc ?? StrCast(this._props.Document._viewTransition);
                     const durationMiliStr = viewTrans.match(/([0-9]*)ms/);
                     const durationSecStr = viewTrans.match(/([0-9.]*)s/);
                     const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0;
@@ -268,8 +279,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
             eventBus,
         });
         pdfLinkService.setViewer(this._pdfViewer);
-        pdfLinkService.setDocument(this.props.pdf, null);
-        this._pdfViewer.setDocument(this.props.pdf);
+        pdfLinkService.setDocument(this._props.pdf, null);
+        this._pdfViewer.setDocument(this._props.pdf);
     }
 
     @action
@@ -303,12 +314,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (this._mainCont.current && !this._forcedScroll) {
             this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
             if (!LinkDocPreview.LinkInfo) {
-                this.props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
+                this._props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
             }
             this._ignoreScroll = false;
             if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio
             this._scrollTimer = setTimeout(() => {
-                DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
+                DocUtils.MakeLinkToActiveAudio(() => this._props.DocumentView?.().ComponentView?.getAnchor!(true)!, false);
                 this._scrollTimer = undefined;
             }, 200);
         }
@@ -358,12 +369,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
         // if alt+left click, drag and annotate
         this._downX = e.clientX;
         this._downY = e.clientY;
-        if ((this.props.Document._freeform_scale || 1) !== 1) return;
-        if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) {
-            this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this.props.Document);
+        if ((this._props.Document._freeform_scale || 1) !== 1) return;
+        if ((e.button !== 0 || e.altKey) && this._props.isContentActive(true)) {
+            this._setPreviewCursor?.(e.clientX, e.clientY, true, false, this._props.Document);
         }
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
-            this.props.select(false);
+        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+            this._props.select(false);
             MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
             this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]);
             this.isAnnotating = true;
@@ -393,7 +404,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
         this.isAnnotating = false;
         clearStyleSheetRules(PDFViewer._annotationStyle);
-        this.props.select(false);
+        this._props.select(false);
         document.removeEventListener('pointerup', this.onSelectEnd);
 
         const sel = window.getSelection();
@@ -408,21 +419,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
         // Changing which document to add the annotation to (the currently selected PDF)
         GPTPopup.Instance.setSidebarId('data_sidebar');
-        GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
+        GPTPopup.Instance.addDoc = this._props.sidebarAddDoc;
     };
 
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
-            this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
+            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
-                if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this.props.ScreenToLocalTransform().Scale) {
+                if (rect && rect?.width && rect.width < this._mainCont.current.clientWidth / this._props.ScreenToLocalTransform().Scale) {
                     const scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
                     const scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
-                    const pdfScale = NumCast(this.props.layoutDoc._freeform_scale, 1);
+                    const pdfScale = NumCast(this._props.layoutDoc._freeform_scale, 1);
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
                     // transforms the positions from screen onto the pdf div
@@ -451,7 +462,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     onClick = (e: React.MouseEvent) => {
         this._scrollStopper?.();
         if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
-            this._setPreviewCursor(e.clientX, e.clientY, false, false, this.props.Document);
+            this._setPreviewCursor(e.clientX, e.clientY, false, false, this._props.Document);
         }
         // e.stopPropagation();  // bcz: not sure why this was here.  We need to allow the DocumentView to get clicks to process doubleClicks
     };
@@ -460,48 +471,48 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this.props.isContentActive(true)) {
+        if (this._props.isContentActive(true)) {
             e.stopPropagation();
             if (e.ctrlKey) {
                 const curScale = Number(this._pdfViewer.currentScaleValue);
                 this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - (curScale * e.deltaY) / 1000));
-                this.props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
+                this._props.layoutDoc._freeform_scale = Number(this._pdfViewer.currentScaleValue);
             }
         }
     };
 
     pointerEvents = () =>
-        this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
+        this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown()
             ? 'all' //
             : 'none';
     @computed get annotationLayer() {
         const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden);
         return (
-            <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
+            <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}>
                 {inlineAnnos.map(anno => (
-                    <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                    <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                 ))}
             </div>
         );
     }
 
     getScrollHeight = () => this._scrollHeight;
-    scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._layout_scrollTop)) : this.props.ScreenToLocalTransform());
-    overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._freeform_scale, 1));
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this.props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
+    scrollXf = () => (this._mainCont.current ? this._props.ScreenToLocalTransform().translate(0, NumCast(this._props.layoutDoc._layout_scrollTop)) : this._props.ScreenToLocalTransform());
+    overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
+    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
+    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
-            if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none';
+            if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
             const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
             return isInk ? 'visiblePainted' : 'all';
         }
-        return this.props.styleProvider?.(doc, props, property);
+        return this._props.styleProvider?.(doc, props, property);
     };
 
-    childPointerEvents = () => (this.props.isContentActive() !== false ? 'all' : 'none');
+    childPointerEvents = () => (this._props.isContentActive() !== false ? 'all' : 'none');
     renderAnnotations = (childFilters: () => string[], mixBlendMode?: any, display?: string) => (
         <div
             className="pdfViewerDash-overlay"
@@ -511,15 +522,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
-                {...this.props}
+                {...this._props}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 setContentView={emptyFunction} // override setContentView to do nothing
-                pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+                pointerEvents={this._props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
                 childPointerEvents={this.childPointerEvents} // but freeform children need to get events to allow text editing, etc
-                renderDepth={this.props.renderDepth + 1}
+                renderDepth={this._props.renderDepth + 1}
                 isAnnotationOverlay={true}
-                fieldKey={this.props.fieldKey + '_annotations'}
+                fieldKey={this._props.fieldKey + '_annotations'}
                 getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
                 PanelHeight={this.panelHeight}
@@ -535,39 +546,39 @@ export class PDFViewer extends React.Component<IViewerProps> {
         </div>
     );
     @computed get overlayTransparentAnnotations() {
-        const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []);
-        return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this.props.isContentActive() ? 'none' : undefined);
+        const transparentChildren = DocUtils.FilterDocs(DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']), this.transparentFilter(), []);
+        return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', SnappingManager.GetCanEmbed() && this._props.isContentActive() ? 'none' : undefined);
     }
     @computed get overlayOpaqueAnnotations() {
         return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined);
     }
     @computed get overlayLayer() {
         return (
-            <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
+            <div style={{ pointerEvents: this._props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}>
                 {this.overlayTransparentAnnotations}
                 {this.overlayOpaqueAnnotations}
             </div>
         );
     }
     @computed get pdfViewerDiv() {
-        return <div className={'pdfViewerDash-text' + (this.props.pointerEvents?.() !== 'none' && this._textSelecting && this.props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
+        return <div className={'pdfViewerDash-text' + (this._props.pointerEvents?.() !== 'none' && this._textSelecting && this._props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
     }
     savedAnnotations = () => this._savedAnnotations;
-    addDocumentWrapper = (doc: Doc | Doc[]) => this.props.addDocument!(doc);
+    addDocumentWrapper = (doc: Doc | Doc[]) => this._props.addDocument!(doc);
     render() {
         TraceMobx();
         return (
             <div className="pdfViewer-content">
                 <div
-                    className={`pdfViewerDash${this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
+                    className={`pdfViewerDash${this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' ? '-interactive' : ''}`}
                     ref={this._mainCont}
                     onScroll={this.onScroll}
                     onWheel={this.onZoomWheel}
                     onPointerDown={this.onPointerDown}
                     onClick={this.onClick}
                     style={{
-                        overflowX: NumCast(this.props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
-                        height: !this.props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`,
+                        overflowX: NumCast(this._props.layoutDoc._freeform_scale, 1) !== 1 ? 'scroll' : undefined,
+                        height: !this._props.Document._layout_fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this._props.Document) : `100%`,
                     }}>
                     {this.pdfViewerDiv}
                     {this.annotationLayer}
@@ -576,13 +587,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
-                            Document={this.props.Document}
+                            Document={this._props.Document}
                             getPageFromScroll={this.getPageFromScroll}
-                            anchorMenuClick={this.props.anchorMenuClick}
+                            anchorMenuClick={this._props.anchorMenuClick}
                             scrollTop={0}
-                            annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)}
+                            annotationLayerScrollTop={NumCast(this._props.Document._layout_scrollTop)}
                             addDocument={this.addDocumentWrapper}
-                            docView={this.props.DocumentView!}
+                            docView={this._props.DocumentView!}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotations}
                             selectionText={this.selectionText}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 7f71ee678..d1c039d95 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -19,7 +19,6 @@ import { SharingManager } from '../../util/SharingManager';
 import { Transform } from '../../util/Transform';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionLinearView } from '../collections/collectionLinear';
-import { ContextMenu } from '../ContextMenu';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
 import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 6fb97d70c..5449c8dea 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,5 +1,5 @@
 import { saveAs } from 'file-saver';
-import { action, computed, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { alias, map, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
@@ -131,9 +131,9 @@ export function updateCachedAcls(doc: Doc) {
 @Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
     @observable public static RecordingEvent = 0;
-    @observable public static GuestDashboard: Doc | undefined;
-    @observable public static GuestTarget: Doc | undefined;
-    @observable public static GuestMobile: Doc | undefined;
+    @observable public static GuestDashboard: Doc | undefined = undefined;
+    @observable public static GuestTarget: Doc | undefined = undefined;
+    @observable public static GuestMobile: Doc | undefined = undefined;
     public static CurrentUserEmail: string = '';
 
     public static get MySharedDocs()          { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
@@ -178,6 +178,7 @@ export class Doc extends RefField {
 
     constructor(id?: FieldId, forceSave?: boolean) {
         super(id);
+        makeObservable(this);
         const docProxy = new Proxy<this>(this, {
             set: setter,
             get: getter,
@@ -185,7 +186,36 @@ export class Doc extends RefField {
             has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fieldTuples,
             ownKeys: target => {
                 const keys = GetEffectiveAcl(target) !== AclPrivate ? Object.keys(target[FieldKeys]) : [];
-                return [...keys, '__LAYOUT__'];
+                return [
+                    ...keys,
+                    AclAdmin,
+                    AclAugment,
+                    AclEdit,
+                    AclPrivate,
+                    AclReadonly,
+                    Animation,
+                    AudioPlay,
+                    Brushed,
+                    CachedUpdates,
+                    DirectLinks,
+                    DocAcl,
+                    DocCss,
+                    DocData,
+                    DocFields,
+                    DocLayout,
+                    DocViews,
+                    FieldKeys,
+                    FieldTuples,
+                    ForceServerWrite,
+                    Height,
+                    Highlight,
+                    Initializing,
+                    Self,
+                    SelfProxy,
+                    UpdatingFromServer,
+                    Width,
+                    '__LAYOUT__',
+                ];
             },
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop.toString() === '__LAYOUT__' || !(prop in target[FieldKeys])) {
diff --git a/src/fields/List.ts b/src/fields/List.ts
index da007e972..8c8ff1ea3 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
@@ -233,12 +233,13 @@ class ListImpl<T extends Field> extends ObjectField {
     }
     constructor(fields?: T[]) {
         super();
+        makeObservable(this);
         const list = new Proxy<this>(this, {
             set: setter,
             get: ListImpl.listGetter,
             ownKeys: target => {
                 const keys = Object.keys(target.__fieldTuples);
-                return [...keys, '__realFields'];
+                return [...keys, FieldTuples, Self, SelfProxy, '__realFields'];
             },
             getOwnPropertyDescriptor: (target, prop) => {
                 if (prop in target[FieldTuples]) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ca02284da..545fe4478 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -21,7 +21,7 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+var tracing = false;
 export function TraceMobx() {
     tracing && trace();
 }
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index c0da39a03..4c2004681 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -1,23 +1,23 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _error, _success, _invalid } from "../RouteManager";
-import * as path from "path";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import { BatchedArray, TimeUnit } from "array-batcher";
-import { Opt } from "../../fields/Doc";
-import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils";
-import { Database } from "../database";
-import { red } from "colors";
-import { Upload } from "../SharedMediaTypes";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _error, _success, _invalid } from '../RouteManager';
+import * as path from 'path';
+import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
+import { BatchedArray, TimeUnit } from 'array-batcher';
+import { Opt } from '../../fields/Doc';
+import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
+import { Database } from '../database';
+import { red } from 'colors';
+import { Upload } from '../SharedMediaTypes';
 import * as request from 'request-promise';
-import { NewMediaItemResult } from "../apis/google/SharedTypes";
+import { NewMediaItemResult } from '../apis/google/SharedTypes';
 
-const prefix = "google_photos_";
+const prefix = 'google_photos_';
 const remoteUploadError = "None of the preliminary uploads to Google's servers was successful.";
-const authenticationError = "Unable to authenticate Google credentials before uploading to Google Photos!";
-const mediaError = "Unable to convert all uploaded bytes to media items!";
+const authenticationError = 'Unable to authenticate Google credentials before uploading to Google Photos!';
+const mediaError = 'Unable to convert all uploaded bytes to media items!';
 const localUploadError = (count: number) => `Unable to upload ${count} images to Dash's server`;
 const requestError = "Unable to execute download: the body's media items were malformed.";
-const downloadError = "Encountered an error while executing downloads.";
+const downloadError = 'Encountered an error while executing downloads.';
 
 interface GooglePhotosUploadFailure {
     batch: number;
@@ -41,17 +41,15 @@ interface NewMediaItem {
  * This manager handles the creation of routes for google photos functionality.
  */
 export default class GooglePhotosManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         /**
          * This route receives a list of urls that point to images stored
          * on Dash's file system, and, in a two step process, uploads them to Google's servers and
-         * returns the information Google generates about the associated uploaded remote images. 
+         * returns the information Google generates about the associated uploaded remote images.
          */
         register({
             method: Method.POST,
-            subscription: "/googlePhotosMediaPost",
+            subscription: '/googlePhotosMediaPost',
             secureHandler: async ({ user, req, res }) => {
                 const { media } = req.body;
 
@@ -67,38 +65,35 @@ export default class GooglePhotosManager extends ApiManager {
                 const failed: GooglePhotosUploadFailure[] = [];
                 const batched = BatchedArray.from<Uploader.UploadSource>(media, { batchSize: 25 });
                 const interval = { magnitude: 100, unit: TimeUnit.Milliseconds };
-                const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(
-                    interval,
-                    async (batch, collector, { completedBatches }) => {
-                        for (let index = 0; index < batch.length; index++) {
-                            const { url, description } = batch[index];
-                            // a local function used to record failure of an upload
-                            const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url });
-                            // see image resizing - we store the size-agnostic url in our logic, but write out size-suffixed images to the file system
-                            // so here, given a size agnostic url, we're just making that conversion so that the file system knows which bytes to actually upload
-                            const imageToUpload = InjectSize(url, SizeSuffix.Original);
-                            // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token
-                            // which acts as a pointer to those bytes that we can use to locate them later on
-                            const uploadToken = await Uploader.SendBytes(token, imageToUpload).catch(fail);
-                            if (!uploadToken) {
-                                fail(`${path.extname(url)} is not an accepted extension`);
-                            } else {
-                                // gather the upload token return from Google (a pointer they give us to the raw, currently useless bytes
-                                // we've uploaded to their servers) and put in the JSON format that the API accepts for image creation (used soon, below)
-                                collector.push({
-                                    description,
-                                    simpleMediaItem: { uploadToken }
-                                });
-                            }
+                const newMediaItems = await batched.batchedMapPatientInterval<NewMediaItem>(interval, async (batch, collector, { completedBatches }) => {
+                    for (let index = 0; index < batch.length; index++) {
+                        const { url, description } = batch[index];
+                        // a local function used to record failure of an upload
+                        const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url });
+                        // see image resizing - we store the size-agnostic url in our logic, but write out size-suffixed images to the file system
+                        // so here, given a size agnostic url, we're just making that conversion so that the file system knows which bytes to actually upload
+                        const imageToUpload = InjectSize(url, SizeSuffix.Original);
+                        // STEP 1/2: send the raw bytes of the image from our server to Google's servers. We'll get back an upload token
+                        // which acts as a pointer to those bytes that we can use to locate them later on
+                        const uploadToken = await Uploader.SendBytes(token, imageToUpload).catch(fail);
+                        if (!uploadToken) {
+                            fail(`${path.extname(url)} is not an accepted extension`);
+                        } else {
+                            // gather the upload token return from Google (a pointer they give us to the raw, currently useless bytes
+                            // we've uploaded to their servers) and put in the JSON format that the API accepts for image creation (used soon, below)
+                            collector.push({
+                                description,
+                                simpleMediaItem: { uploadToken },
+                            });
                         }
                     }
-                );
+                });
 
                 // inform the developer / server console of any failed upload attempts
                 // does not abort the operation, since some subset of the uploads may have been successful
                 const { length } = failed;
                 if (length) {
-                    console.error(`Unable to upload ${length} image${length === 1 ? "" : "s"} to Google's servers`);
+                    console.error(`Unable to upload ${length} image${length === 1 ? '' : 's'} to Google's servers`);
                     console.log(failed.map(({ reason, batch, index, url }) => `@${batch}.${index}: ${url} failed:\n${reason}`).join('\n\n'));
                 }
 
@@ -115,7 +110,7 @@ export default class GooglePhotosManager extends ApiManager {
                     results => _success(res, { results, failed }),
                     error => _error(res, mediaError, error)
                 );
-            }
+            },
         });
 
         /**
@@ -128,11 +123,11 @@ export default class GooglePhotosManager extends ApiManager {
          * since the same bytes on their server might now be associated with a new, random url.
          * So, we do the next best thing and try to use an intrinsic attribute of those bytes as
          * an identifier: the precise content size. This works in small cases, but has the obvious flaw of failing to upload
-         * an image locally if we already have uploaded another Google user content image with the exact same content size.   
+         * an image locally if we already have uploaded another Google user content image with the exact same content size.
          */
         register({
             method: Method.POST,
-            subscription: "/googlePhotosMediaGet",
+            subscription: '/googlePhotosMediaGet',
             secureHandler: async ({ req, res }) => {
                 const { mediaItems } = req.body as { mediaItems: MediaItem[] };
                 if (!mediaItems) {
@@ -167,7 +162,7 @@ export default class GooglePhotosManager extends ApiManager {
                             failed++;
                         }
                     } else {
-                        // if we have, the variable 'found' is handily the upload information of the 
+                        // if we have, the variable 'found' is handily the upload information of the
                         // existing image, so we add it to the list as if we had just uploaded it now without actually
                         // making a duplicate write
                         completed.push(found);
@@ -180,9 +175,8 @@ export default class GooglePhotosManager extends ApiManager {
                 // otherwise, return the image upload information list corresponding to the newly (or previously)
                 // uploaded images
                 _success(res, completed);
-            }
+            },
         });
-
     }
 }
 
@@ -192,11 +186,10 @@ export default class GooglePhotosManager extends ApiManager {
  * and then initialize / create those images in the Photos
  * API given the upload tokens returned from the initial
  * uploading process.
- * 
+ *
  * https://developers.google.com/photos/library/reference/rest/v1/mediaItems/batchCreate
  */
 export namespace Uploader {
-
     /**
      * Specifies the structure of the object
      * necessary to upload bytes to Google's servers.
@@ -214,7 +207,7 @@ export namespace Uploader {
      * into the BatchCreate API request
      * to take a reference to raw uploaded bytes
      * and actually create an image in Google Photos.
-     * 
+     *
      * So, to instantiate this interface you must have already dispatched an upload
      * and received an upload token.
      */
@@ -245,7 +238,7 @@ export namespace Uploader {
     function headers(type: string, token: string) {
         return {
             'Content-Type': `application/${type}`,
-            'Authorization': `Bearer ${token}`,
+            Authorization: `Bearer ${token}`,
         };
     }
 
@@ -272,17 +265,19 @@ export namespace Uploader {
             headers: {
                 ...headers('octet-stream', bearerToken),
                 'X-Goog-Upload-File-Name': filename || path.basename(url),
-                'X-Goog-Upload-Protocol': 'raw'
+                'X-Goog-Upload-Protocol': 'raw',
             },
-            body
+            body,
         };
-        return new Promise((resolve, reject) => request(parameters, (error, _response, body) => {
-            if (error) {
-                // on rejection, the server logs the error and the offending image
-                return reject(error);
-            }
-            resolve(body);
-        }));
+        return new Promise((resolve, reject) =>
+            request(parameters, (error, _response, body) => {
+                if (error) {
+                    // on rejection, the server logs the error and the offending image
+                    return reject(error);
+                }
+                resolve(body);
+            })
+        );
     };
 
     /**
@@ -303,19 +298,18 @@ export namespace Uploader {
         // seems to need at least some latency between requests (spamming it synchronously has led to the server returning errors)...
         const batched = BatchedArray.from(newMediaItems, { batchSize: 50 });
         // ...so we execute them in delayed batches and await the entire execution
-        return batched.batchedMapPatientInterval(
-            { magnitude: 100, unit: TimeUnit.Milliseconds },
-            async (batch: NewMediaItem[], collector): Promise<void> => {
-                const parameters = {
-                    method: 'POST',
-                    headers: headers('json', bearerToken),
-                    uri: prepend('mediaItems:batchCreate'),
-                    body: { newMediaItems: batch } as any,
-                    json: true
-                };
-                // register the target album, if provided
-                album && (parameters.body.albumId = album.id);
-                collector.push(...(await new Promise<NewMediaItemResult[]>((resolve, reject) => {
+        return batched.batchedMapPatientInterval({ magnitude: 100, unit: TimeUnit.Milliseconds }, async (batch: NewMediaItem[], collector): Promise<void> => {
+            const parameters = {
+                method: 'POST',
+                headers: headers('json', bearerToken),
+                uri: prepend('mediaItems:batchCreate'),
+                body: { newMediaItems: batch } as any,
+                json: true,
+            };
+            // register the target album, if provided
+            album && (parameters.body.albumId = album.id);
+            collector.push(
+                ...(await new Promise<NewMediaItemResult[]>((resolve, reject) => {
                     request(parameters, (error, _response, body) => {
                         if (error) {
                             reject(error);
@@ -323,9 +317,8 @@ export namespace Uploader {
                             resolve(body.newMediaItemResults);
                         }
                     });
-                })));
-            }
-        );
+                }))
+            );
+        });
     };
-
-}
\ No newline at end of file
+}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 7765349ff..dd2a857d9 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -25,6 +25,7 @@ const ffmpeg = require('fluent-ffmpeg');
 const fs = require('fs');
 const requestImageSize = require('../client/util/request-image-size');
 const md5File = require('md5-file');
+const autorotate = require('jpeg-autorotate');
 
 export enum SizeSuffix {
     Small = '_s',
@@ -604,17 +605,21 @@ export namespace DashUploadUtils {
             sizes.filter(({ width }) => !width).map(({ suffix }) => 
                 new Promise<void>(res => createReadStream(sourcePath).pipe(createWriteStream(outputPath(suffix))).on('close', res))
         )); // prettier-ignore
-        return Jimp.read(sourcePath)
-            .then(async img => {
-                await Promise.all( sizes.filter(({ width }) => width)  .map(({ width, suffix }) => 
-                    img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
-                )); // prettier-ignore
-                return writtenFiles;
-            })
-            .catch(e => {
-                console.log('ERROR' + e);
-                return writtenFiles;
-            });
+
+        const fileIn = fs.readFileSync(sourcePath);
+        return autorotate.rotate(fileIn, { quality: 30 }).then(({ buffer }: { buffer: any }) =>
+            Jimp.read(buffer)
+                .then(async img => {
+                    await Promise.all( sizes.filter(({ width }) => width)  .map(({ width, suffix }) => 
+                        img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
+                    )); // prettier-ignore
+                    return writtenFiles;
+                })
+                .catch(e => {
+                    console.log('ERROR' + e);
+                    return writtenFiles;
+                })
+        );
     }
 
     /**
diff --git a/tsconfig.json b/tsconfig.json
index 1e8934ee8..8014877f5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,7 @@
         "experimentalDecorators": true,
         "allowSyntheticDefaultImports": true,
         "moduleDetection": "auto",
+        "useDefineForClassFields": false, // allowing this to be true breaks overriding of observable props in subclasses (see CollectionSubView)
         "strict": true,
         "jsx": "react",
         "allowJs": true,
@@ -13,7 +14,6 @@
         "outDir": "dist",
         "lib": ["dom", "es2017"],
         "typeRoots": ["node_modules/@types", "./src/typings"],
-        "types": ["youtube", "node"],
         "resolveJsonModule": true,
         "moduleResolution": "node"
     },
diff --git a/webpack.config.js b/webpack.config.js
index 2085c297c..57dfd222b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -59,7 +59,7 @@ module.exports = {
         publicPath: '/',
     },
     resolve: {
-        extensions: ['.js', '.ts', '.tsx'],
+        extensions: ['.js', '.jsx', '.ts', '.tsx'],
         fallback: {
             fs: false,
             tls: false,
@@ -91,6 +91,11 @@ module.exports = {
                     },
                 ],
             },
+            {
+                test: /\.jsx$/,
+                use: { loader: 'babel-loader' },
+                exclude: [/node_modules/, /public/],
+            },
             {
                 test: /\.m?js/,
                 resolve: {
-- 
cgit v1.2.3-70-g09d2


From 07c30aed69dfa810ddb0b861ae2dc8e8f3a27356 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 11 Dec 2023 17:04:49 -0500
Subject: fixed includes of scss globals into typescript files.

---
 package-lock.json                                  |   3 +-
 package.json                                       |   2 +-
 src/client/documents/Documents.ts                  |   5 +-
 src/client/util/CaptureManager.scss                |   7 +-
 src/client/util/DragManager.ts                     |   3 +-
 src/client/util/SettingsManager.scss               |   4 +-
 src/client/util/reportManager/ReportManager.scss   |   7 +-
 src/client/views/AntimodeMenu.scss                 |  11 +--
 src/client/views/ContextMenu.scss                  |   2 +-
 src/client/views/DashboardView.scss                |  52 +++++------
 src/client/views/DocumentButtonBar.scss            |   2 +-
 src/client/views/DocumentDecorations.scss          |   2 +-
 src/client/views/InkingStroke.tsx                  |   4 +-
 src/client/views/Main.scss                         |   2 +-
 src/client/views/MainView.scss                     |   2 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/PropertiesButtons.scss            |  45 +++++----
 src/client/views/PropertiesSection.scss            |   7 +-
 src/client/views/PropertiesView.scss               |   2 +-
 src/client/views/TemplateMenu.scss                 |   7 +-
 src/client/views/_nodeModuleOverrides.scss         |   2 +-
 src/client/views/animationtimeline/Region.scss     |   2 +-
 src/client/views/animationtimeline/Region.tsx      |   2 +-
 src/client/views/animationtimeline/Timeline.scss   |  18 +---
 .../views/animationtimeline/TimelineMenu.scss      |  78 +++++++---------
 .../views/animationtimeline/TimelineOverview.scss  |   6 +-
 src/client/views/animationtimeline/Track.scss      |   5 +-
 .../collections/CollectionCarousel3DView.scss      |   6 +-
 .../views/collections/CollectionCarousel3DView.tsx |   5 +-
 .../views/collections/CollectionDockingView.scss   |   2 +-
 src/client/views/collections/CollectionMenu.scss   |   4 +-
 .../collections/CollectionNoteTakingView.scss      |   2 +-
 .../collections/CollectionStackedTimeline.scss     |   2 +-
 .../views/collections/CollectionStackingView.scss  |   2 +-
 .../views/collections/CollectionTreeView.scss      |   2 +-
 src/client/views/collections/CollectionView.scss   |   2 +-
 src/client/views/collections/TabDocView.scss       |   2 +-
 src/client/views/collections/TreeView.scss         |   2 +-
 src/client/views/collections/TreeView.tsx          |   3 +-
 .../CollectionFreeFormRemoteCursors.scss           |  10 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |   2 +-
 .../collectionLinear/CollectionLinearView.scss     |   2 +-
 .../collectionSchema/CollectionSchemaView.scss     |   2 +-
 .../views/global/globalCssVariables.module.scss    |  96 +++++++++++++++++++
 .../global/globalCssVariables.module.scss.d.ts     |  21 +++++
 src/client/views/global/globalCssVariables.scss    |  96 -------------------
 .../views/global/globalCssVariables.scss.d.ts      |  21 -----
 src/client/views/linking/LinkMenu.scss             |   6 +-
 src/client/views/linking/LinkMenuItem.scss         |   2 +-
 src/client/views/nodes/AudioBox.scss               |  10 +-
 .../views/nodes/DataVizBox/components/Chart.scss   |   2 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   4 +-
 src/client/views/nodes/DocumentLinksButton.scss    |   2 +-
 src/client/views/nodes/DocumentView.scss           |   2 +-
 src/client/views/nodes/EquationBox.scss            |   2 +-
 .../views/nodes/FontIconBox/FontIconBox.scss       |   2 +-
 src/client/views/nodes/KeyValueBox.scss            |  54 +++++------
 src/client/views/nodes/KeyValuePair.scss           |   2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |   7 +-
 src/client/views/nodes/LinkDescriptionPopup.scss   |   8 +-
 src/client/views/nodes/MapBox/MapBox.scss          |   2 +-
 src/client/views/nodes/PDFBox.scss                 |  16 +---
 src/client/views/nodes/RadialMenu.scss             |  14 ++-
 src/client/views/nodes/VideoBox.scss               |   8 +-
 src/client/views/nodes/WebBox.scss                 |   2 +-
 .../views/nodes/formattedText/DashFieldView.scss   |   2 +-
 .../nodes/formattedText/FormattedTextBox.scss      |   4 +-
 .../views/nodes/formattedText/RichTextMenu.scss    |  11 +--
 .../views/nodes/formattedText/TooltipTextMenu.scss | 102 ++++++++++-----------
 src/client/views/nodes/trails/PresBox.scss         |   2 +-
 src/client/views/search/CheckBox.scss              |   7 +-
 src/client/views/search/CollectionFilters.scss     |   4 +-
 src/client/views/search/IconBar.scss               |   4 +-
 src/client/views/search/IconButton.scss            |   8 +-
 src/client/views/search/IconButton.tsx             |   2 +-
 src/client/views/search/NaviconButton.scss         |   2 +-
 src/client/views/search/SearchBox.scss             |  17 ++--
 src/client/views/search/SelectorContextMenu.scss   |   4 +-
 src/client/views/search/ToggleBar.scss             |   4 +-
 src/client/views/topbar/TopBar.scss                |   3 +-
 src/client/views/webcam/DashWebRTCVideo.scss       |   9 +-
 src/mobile/ImageUpload.scss                        |  13 +--
 src/mobile/ImageUpload.tsx                         |   3 +-
 webpack.config.js                                  |  28 +++---
 84 files changed, 450 insertions(+), 494 deletions(-)
 create mode 100644 src/client/views/global/globalCssVariables.module.scss
 create mode 100644 src/client/views/global/globalCssVariables.module.scss.d.ts
 delete mode 100644 src/client/views/global/globalCssVariables.scss
 delete mode 100644 src/client/views/global/globalCssVariables.scss.d.ts

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index 84b087892..4af642fc4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -176,6 +176,7 @@
                 "reveal.js": "^5.0.2",
                 "rimraf": "^5.0.5",
                 "sass": "^1.69.5",
+                "sass-loader": "^13.3.2",
                 "serializr": "^3.0.2",
                 "shelljs": "^0.8.5",
                 "socket.io": "^4.7.2",
@@ -269,7 +270,6 @@
                 "mocha": "^10.2.0",
                 "prettier": "^3.1.0",
                 "react-type-animation": "^3.2.0",
-                "sass-loader": "^13.3.2",
                 "scss-loader": "0.0.1",
                 "style-loader": "^3.3.3",
                 "ts-loader": "^9.5.1",
@@ -25578,7 +25578,6 @@
             "version": "13.3.2",
             "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz",
             "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==",
-            "dev": true,
             "dependencies": {
                 "neo-async": "^2.6.2"
             },
diff --git a/package.json b/package.json
index be944ff01..e99bf3734 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,6 @@
         "mocha": "^10.2.0",
         "prettier": "^3.1.0",
         "react-type-animation": "^3.2.0",
-        "sass-loader": "^13.3.2",
         "scss-loader": "0.0.1",
         "style-loader": "^3.3.3",
         "ts-loader": "^9.5.1",
@@ -262,6 +261,7 @@
         "reveal.js": "^5.0.2",
         "rimraf": "^5.0.5",
         "sass": "^1.69.5",
+        "sass-loader": "^13.3.2",
         "serializr": "^3.0.2",
         "shelljs": "^0.8.5",
         "socket.io": "^4.7.2",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1b39fdb41..dd6d6cdf6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -58,8 +58,9 @@ import { VideoBox } from '../views/nodes/VideoBox';
 import { WebBox } from '../views/nodes/WebBox';
 import { SearchBox } from '../views/search/SearchBox';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
-//import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
-const DFLT_IMAGE_NATIVE_DIM = '900px';
+const {
+    default: { DFLT_IMAGE_NATIVE_DIM },
+} = require('../views/global/globalCssVariables.module.scss');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', ''));
 class EmptyBox {
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index 11e31fe2e..8679a0101 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -1,4 +1,4 @@
-@import "../views/global/globalCssVariables";
+@import '../views/global/globalCssVariables.module';
 
 .capture-interface {
     //background-color: whitesmoke !important;
@@ -39,7 +39,7 @@
         align-items: center;
         justify-content: center;
     }
-    
+
     .recordButtonInner {
         border-radius: 100%;
         width: 70%;
@@ -106,7 +106,7 @@
             display: flex;
             justify-content: center;
             align-items: center;
-            background-color: #BDDBE8;
+            background-color: #bddbe8;
             border-radius: 100%;
             font-weight: 800;
             margin-right: 5px;
@@ -154,4 +154,3 @@
         }
     }
 }
-
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 427b1c85f..4b1cc1702 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -13,8 +13,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
-// import * as globalCssVariables from '../views/global/globalCssVariables.scss';
-const contextMenuZindex = 100002;
+const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
 
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index bca649bc3..dbfc48c63 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -1,4 +1,4 @@
-@import '../views/global/globalCssVariables';
+@import '../views/global/globalCssVariables.module';
 
 .settings-interface {
     //background-color: whitesmoke !important;
@@ -187,14 +187,12 @@
     display: flex;
     flex-direction: column;
 
-
     .close-button {
         position: absolute;
         right: 2px;
         top: 2px;
     }
 
-
     .settings-content {
         padding: 10px;
         width: 500px;
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
index cd6a1d934..d82d7fdeb 100644
--- a/src/client/util/reportManager/ReportManager.scss
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -1,4 +1,4 @@
-@import '../../views/global/globalCssVariables';
+@import '../../views/global/globalCssVariables.module';
 
 // header
 
@@ -360,5 +360,8 @@
     padding: 4px 10px;
     font-size: 10px;
     border-radius: 32px;
-    transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
+    transition:
+        background-color 0.2s ease,
+        color 0.2s ease,
+        border-color 0.2s ease;
 }
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index b205a0f1e..4613cec76 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -1,5 +1,4 @@
-@import "./global/globalCssVariables";
-
+@import './global/globalCssVariables.module';
 
 .antimodeMenu-cont {
     position: absolute;
@@ -16,7 +15,7 @@
     gap: 3px;
 
     &.with-rows {
-        flex-direction: column
+        flex-direction: column;
     }
 
     .antimodeMenu-row {
@@ -26,8 +25,8 @@
 
     .antimodeMenu-dragger {
         height: 100%;
-        transition: width .2s;
-        background-image: url("https://logodix.com/logo/1020374.png");
+        transition: width 0.2s;
+        background-image: url('https://logodix.com/logo/1020374.png');
         background-size: 90% 100%;
         background-repeat: no-repeat;
         background-position: left center;
@@ -63,4 +62,4 @@
             background-color: #121212;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 588eff1d1..232362c5c 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module.scss';
 
 .contextMenu-cont {
     position: absolute;
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
index 6be2133ef..90f64b393 100644
--- a/src/client/views/DashboardView.scss
+++ b/src/client/views/DashboardView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables';
+@import './global/globalCssVariables.module';
 
 .dashboard-view {
     padding: 50px;
@@ -7,18 +7,18 @@
     width: 100%;
     position: absolute;
     height: 100%;
-    width:100%;
+    width: 100%;
     padding-right: 0px;
     overflow: auto;
 
-      .left-menu {
-            display: flex;
-            justify-content: flex-start;
-            flex-direction: column;
-            width: 250px;
-            min-width: 250px;
-            gap: 5px;
-      }
+    .left-menu {
+        display: flex;
+        justify-content: flex-start;
+        flex-direction: column;
+        width: 250px;
+        min-width: 250px;
+        gap: 5px;
+    }
 
     .all-dashboards {
         display: flex;
@@ -75,20 +75,20 @@
         left: 0;
         top: 0;
         z-index: -1;
-      }
+    }
 }
 
 .dashboard-container {
-      border-radius: 10px;
-      position: relative;
-      cursor: pointer;
-      width: 250px;
-      height: 200px;
-      outline: solid 2px $light-gray;
-      display: flex;
-      flex-direction: column;
-      margin: 0 0px 30px 30px;
-      overflow: hidden;
+    border-radius: 10px;
+    position: relative;
+    cursor: pointer;
+    width: 250px;
+    height: 200px;
+    outline: solid 2px $light-gray;
+    display: flex;
+    flex-direction: column;
+    margin: 0 0px 30px 30px;
+    overflow: hidden;
 
     &:hover {
         outline: solid 2px $light-blue;
@@ -122,18 +122,18 @@
         background: 'lightgreen';
     }
 
-      .more {
-            z-index: 100;
-      }
+    .more {
+        z-index: 100;
+    }
 
-      .background {
+    .background {
         position: absolute;
         width: 100%;
         height: 100%;
         left: 0;
         top: 0;
         z-index: -1;
-      }
+    }
 }
 
 .new-dashboard {
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 1abf33cac..11614d627 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
 
 $linkGap: 3px;
 
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index bbd951481..ac0ef054c 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
 
 $linkGap: 3px;
 $headerHeight: 20px;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d34df8742..15df03682 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -46,9 +46,7 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
-// import { INK_MASK_SIZE } from './global/globalCssVariables.scss';
-const INK_MASK_SIZE = 1000;
-
+const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index a403a10e3..02916e48e 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module';
 @import 'nodeModuleOverrides';
 
 :root {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4fb2ac279..28a0f7750 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables';
+@import 'global/globalCssVariables.module.scss';
 @import 'nodeModuleOverrides';
 html {
     overscroll-behavior-x: none;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a8f4020ee..1e4c1e1d3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -69,9 +69,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
-// import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
-const LEFT_MENU_WIDTH = '60px';
-const TOPBAR_HEIGHT = '37px';
+const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index b801b3abf..b8c73b6d3 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -1,6 +1,6 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
 
-$linkGap : 3px;
+$linkGap: 3px;
 
 .propertiesButtons-linkFlyout {
     grid-column: 2/4;
@@ -23,7 +23,7 @@ $linkGap : 3px;
     // margin-right: 7px;
     // margin-left: 8px;
     height: 28px;
-   // width: 226px;//29px;
+    // width: 226px;//29px;
     display: flex;
     align-items: center;
     // height: 25px;
@@ -39,10 +39,9 @@ $linkGap : 3px;
     // font-size: 75%;
     transition: transform 0.2s;
     // text-align: center;
-  
 
     // justify-content: center;
-    
+
     // margin-right: 10px;
     // margin-left: 4px;
 
@@ -55,34 +54,34 @@ $linkGap : 3px;
 .propertiesButtons-linkButton-empty.toggle-on {
     background-color: $medium-blue;
     color: $white;
-    width:100%
+    width: 100%;
 }
 .propertiesButtons-linkButton-empty.toggle-hover {
     background-color: $light-blue;
     color: $black;
-    width:100%
+    width: 100%;
 }
 .propertiesButtons-linkButton-empty.toggle-off {
-    background-color: white;//$dark-gray;
+    background-color: white; //$dark-gray;
     color: black; //white;
-    width:100%
+    width: 100%;
 }
 
 .propertiesButtons-icon {
-    margin-left:8px;
+    margin-left: 8px;
 }
 
 .propertiesButtons {
-    position:relative;
+    position: relative;
     width: 100%;
     //     margin-top: 3px;
-//    // grid-column: 1/4;
-//     width: 100%;
-//     height: auto;
-//     display: flex;
-//     // flex-direction: row;
-//     // flex-wrap: wrap;
-//     padding-bottom: 5.5px;
+    //    // grid-column: 1/4;
+    //     width: 100%;
+    //     height: auto;
+    //     display: flex;
+    //     // flex-direction: row;
+    //     // flex-wrap: wrap;
+    //     padding-bottom: 5.5px;
 }
 
 .onClickFlyout-editScript {
@@ -95,11 +94,10 @@ $linkGap : 3px;
     padding: 4px;
 }
 
-
 .propertiesButtons-button {
     pointer-events: auto;
-    padding-right: 8px;//5px;
-    width: 100%;//width: 25px;
+    padding-right: 8px; //5px;
+    width: 100%; //width: 25px;
     border-radius: 5px;
     margin-right: 20px;
     margin-bottom: 8px;
@@ -161,12 +159,11 @@ $linkGap : 3px;
     margin-left: 4px;
 
     &:hover {
-        filter:brightness(0.85);
+        filter: brightness(0.85);
         cursor: pointer;
     }
 }
 
-
 @-moz-keyframes spin {
     100% {
         -moz-transform: rotate(360deg);
@@ -194,4 +191,4 @@ $linkGap : 3px;
     100% {
         box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
index 321b6300c..3f92a70f8 100644
--- a/src/client/views/PropertiesSection.scss
+++ b/src/client/views/PropertiesSection.scss
@@ -1,11 +1,10 @@
-@import './global/globalCssVariables.scss';
+@import './global/globalCssVariables.module.scss';
 
 .propertiesView-section {
-    
     .propertiesView-content {
         padding: 10px;
     }
-    
+
     .propertiesView-sectionTitle {
         text-align: center;
         display: flex;
@@ -14,7 +13,7 @@
         font-weight: bold;
         justify-content: space-between;
         align-items: center;
-    
+
         .propertiesView-sectionTitle-icon {
             width: 20px;
             height: 20px;
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 2da2ec568..8581bdf73 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables.scss';
+@import './global/globalCssVariables.module.scss';
 
 .propertiesView-presentationTrails-title {
     display: flex;
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index f81cbdaab..4d0f1bf00 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -1,4 +1,4 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
 .templating-menu {
     position: absolute;
     pointer-events: auto;
@@ -40,7 +40,8 @@
     height: 100%;
     width: 100%;
 
-    .templateToggle, .chromeToggle {
+    .templateToggle,
+    .chromeToggle {
         text-align: left;
         color: black;
     }
@@ -48,4 +49,4 @@
     input {
         margin-right: 10px;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index c99281323..db69d6e44 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables';
+@import './global/globalCssVariables.module.scss';
 // this file is for overriding all the css from installed node modules
 
 // goldenlayout stuff
diff --git a/src/client/views/animationtimeline/Region.scss b/src/client/views/animationtimeline/Region.scss
index f7476ab55..b390ae34e 100644
--- a/src/client/views/animationtimeline/Region.scss
+++ b/src/client/views/animationtimeline/Region.scss
@@ -1,4 +1,4 @@
-@import './../global/globalCssVariables.scss';
+@import './../global/globalCssVariables.module.scss';
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 53c5c4718..bc87dc94d 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -6,7 +6,7 @@ import { List } from '../../../fields/List';
 import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
-import '../global/globalCssVariables.scss';
+import '../global/globalCssVariables.module.scss';
 import './Region.scss';
 import './Timeline.scss';
 import { TimelineMenu } from './TimelineMenu';
diff --git a/src/client/views/animationtimeline/Timeline.scss b/src/client/views/animationtimeline/Timeline.scss
index 48422b789..35ba0fa7f 100644
--- a/src/client/views/animationtimeline/Timeline.scss
+++ b/src/client/views/animationtimeline/Timeline.scss
@@ -1,4 +1,4 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
@@ -30,7 +30,6 @@ $timelineDark: #77a1aa;
             color: $timelineColor;
             margin-left: 3px;
         }
-
     }
 
     .grid-box {
@@ -61,7 +60,7 @@ $timelineDark: #77a1aa;
                 -webkit-transform: scale(1.1);
                 -ms-transform: scale(1.1);
                 transform: scale(1.1);
-                transition: .2s ease;
+                transition: 0.2s ease;
             }
         }
 
@@ -128,7 +127,6 @@ $timelineDark: #77a1aa;
             // margin-top: 0.5px;
         }
     }
-
 }
 
 .time-input {
@@ -154,7 +152,7 @@ $timelineDark: #77a1aa;
 .number-label {
     color: black;
     transform: rotate(-90deg) translate(-15px, 8px);
-    font-size: .85em;
+    font-size: 0.85em;
 }
 
 .timeline-container {
@@ -178,7 +176,6 @@ $timelineDark: #77a1aa;
             background-color: transparent;
             height: 30px;
             width: 100%;
-
         }
 
         .scrubber {
@@ -217,7 +214,6 @@ $timelineDark: #77a1aa;
             position: absolute;
             // box-shadow: -10px 0px 10px 10px red;
         }
-
     }
 
     .currentTime {
@@ -266,7 +262,6 @@ $timelineDark: #77a1aa;
             p {
                 hyphens: auto;
             }
-
         }
     }
 
@@ -280,8 +275,6 @@ $timelineDark: #77a1aa;
     }
 }
 
-
-
 .overview {
     position: absolute;
     height: 50px;
@@ -298,7 +291,6 @@ $timelineDark: #77a1aa;
     }
 }
 
-
 .timeline-checker {
     height: auto;
     width: auto;
@@ -312,11 +304,11 @@ $timelineDark: #77a1aa;
         width: auto;
         overflow: hidden;
         margin: 0px 10px;
-        cursor: pointer
+        cursor: pointer;
     }
 
     .check {
         width: 50px;
         height: 50px;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/TimelineMenu.scss b/src/client/views/animationtimeline/TimelineMenu.scss
index 43a89419e..de2042f17 100644
--- a/src/client/views/animationtimeline/TimelineMenu.scss
+++ b/src/client/views/animationtimeline/TimelineMenu.scss
@@ -1,56 +1,49 @@
-@import "./../global/globalCssVariables.scss"; 
+@import './../global/globalCssVariables.module.scss';
 
-
-.timeline-menu-container{
+.timeline-menu-container {
     position: absolute;
     display: flex;
     box-shadow: $medium-gray 0.2vw 0.2vw 0.4vw;
     flex-direction: column;
     background: whitesmoke;
     z-index: 10000;
-    width: 200px; 
+    width: 200px;
     padding-bottom: 10px;
     border-radius: 15px;
 
-    border: solid #BBBBBBBB 1px;
- 
-
+    border: solid #bbbbbbbb 1px;
 
-    .timeline-menu-input{
-        font: $sans-serif; 
-        font-size: 13px; 
-        width:100%;
-        text-transform: uppercase; 
-        letter-spacing: 2px; 
-        margin-left: 10px; 
-        background-color: transparent; 
-        border-width: 0px; 
-        transition: border-width 500ms; 
+    .timeline-menu-input {
+        font: $sans-serif;
+        font-size: 13px;
+        width: 100%;
+        text-transform: uppercase;
+        letter-spacing: 2px;
+        margin-left: 10px;
+        background-color: transparent;
+        border-width: 0px;
+        transition: border-width 500ms;
     }
 
-    .timeline-menu-input:hover{
-        border-width: 2px; 
+    .timeline-menu-input:hover {
+        border-width: 2px;
     }
-    
-    
-    
 
-    .timeline-menu-header{
-        border-top-left-radius: 15px; 
-        border-top-right-radius: 15px; 
-        text-transform: uppercase; 
-        background: $dark-gray; 
-        letter-spacing: 2px; 
+    .timeline-menu-header {
+        border-top-left-radius: 15px;
+        border-top-right-radius: 15px;
+        text-transform: uppercase;
+        background: $dark-gray;
+        letter-spacing: 2px;
 
-        .timeline-menu-header-desc{
-            font:$sans-serif; 
-            font-size: 13px; 
-            text-align: center; 
-            color: whitesmoke; 
+        .timeline-menu-header-desc {
+            font: $sans-serif;
+            font-size: 13px;
+            text-align: center;
+            color: whitesmoke;
         }
     }
 
-
     .timeline-menu-item {
         // width: 11vw; //10vw
         height: 30px; //2vh
@@ -64,7 +57,7 @@
         -moz-user-select: none;
         -ms-user-select: none;
         user-select: none;
-        transition: all .1s;
+        transition: all 0.1s;
         border-style: none;
         // padding: 10px 0px 10px 0px;
         white-space: nowrap;
@@ -73,22 +66,21 @@
         letter-spacing: 2px;
         text-transform: uppercase;
         padding-right: 20px;
-        padding-left: 10px; 
+        padding-left: 10px;
     }
 
     .timeline-menu-item:hover {
-        border-width: .11px;
+        border-width: 0.11px;
         border-style: none;
         border-color: $medium-gray;
         border-bottom-style: solid;
         border-top-style: solid;
-        background: $medium-blue; 
+        background: $medium-blue;
     }
 
     .timeline-menu-desc {
-        padding-left: 10px; 
-        font:$sans-serif; 
-        font-size: 13px; 
+        padding-left: 10px;
+        font: $sans-serif;
+        font-size: 13px;
     }
-    
-}
\ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/TimelineOverview.scss b/src/client/views/animationtimeline/TimelineOverview.scss
index c8d96c399..2878232e6 100644
--- a/src/client/views/animationtimeline/TimelineOverview.scss
+++ b/src/client/views/animationtimeline/TimelineOverview.scss
@@ -1,4 +1,4 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
@@ -66,8 +66,6 @@ $timelineDark: #77a1aa;
     }
 }
 
-
-
 .timeline-play-bar {
     position: relative;
     padding: 0px;
@@ -104,4 +102,4 @@ $timelineDark: #77a1aa;
     border-radius: 20px;
     margin-top: -4px;
     cursor: pointer;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/animationtimeline/Track.scss b/src/client/views/animationtimeline/Track.scss
index f45e0556d..f56b2fe5f 100644
--- a/src/client/views/animationtimeline/Track.scss
+++ b/src/client/views/animationtimeline/Track.scss
@@ -1,7 +1,6 @@
-@import "./../global/globalCssVariables.scss";
+@import './../global/globalCssVariables.module.scss';
 
 .track-container {
-
     .track {
         .inner {
             top: 0px;
@@ -12,4 +11,4 @@
             z-index: 100;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 1632d44db..a556d0fa7 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 .collectionCarousel3DView-outer {
     height: 100%;
     position: relative;
@@ -17,7 +17,9 @@
     .collectionCarousel3DView-item,
     .collectionCarousel3DView-item-active {
         flex: 1;
-        transition: opacity 0.3s linear, transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+        transition:
+            opacity 0.3s linear,
+            transform 0.5s cubic-bezier(0.455, 0.03, 0.515, 0.955);
         pointer-events: none;
         opacity: 0.5;
         z-index: 1;
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 330cb93e4..f03e38850 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -13,10 +13,7 @@ import { StyleProp } from '../StyleProvider';
 import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
-// import { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } from '../global/globalCssVariables.scss';
-const CAROUSEL3D_CENTER_SCALE = '1';
-const CAROUSEL3D_SIDE_SCALE = '1';
-const CAROUSEL3D_TOP = '0';
+const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class CollectionCarousel3DView extends CollectionSubView() {
     @computed get scrollSpeed() {
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 11543bbe5..7fad3e463 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .lm_root {
     position: relative;
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 6eeccc94e..5d46649b2 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .collectionMenu-container {
     display: flex;
@@ -19,4 +19,4 @@
         display: flex;
         flex-direction: row;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index be1800d81..91a82d40f 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .collectionNoteTakingView-DocumentButtons {
     display: none;
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index a19d8e696..0ced3f9e3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .collectionStackedTimeline-timelineContainer {
     height: 100%;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 165ccbdb1..6225cc52a 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .collectionMasonryView {
     display: inline;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 21efeba44..bbbef78b4 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .collectionTreeView-container {
     transform-origin: top left;
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index 32b0c138d..de53a2c62 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .collectionView {
     border-width: 0;
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index d447991a1..dd4c0b881 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .tabDocView-content {
     height: 100%;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index cbcc7c710..0a1946f09 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .treeView-label {
     max-height: 1.5em;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 27ff7166d..20e7fa071 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -35,8 +35,7 @@ import { CollectionView } from './CollectionView';
 import { TreeSort } from './TreeSort';
 import './TreeView.scss';
 import * as React from 'react';
-// import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
-const TREE_BULLET_WIDTH = '10px';
+const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export interface TreeViewProps {
     treeView: CollectionTreeView;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
index 5fa01b102..7951aff65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -1,14 +1,12 @@
-@import "global/globalCssVariables";
+@import 'global/globalCssVariables.module.scss';
 
 .collectionFreeFormRemoteCursors-cont {
-    
-    position:absolute;
+    position: absolute;
     z-index: $remoteCursors-zindex;
     transform-origin: 'center center';
 }
 .collectionFreeFormRemoteCursors-canvas {
-    
-    position:absolute;
+    position: absolute;
     width: 20px;
     height: 20px;
     opacity: 0.5;
@@ -21,4 +19,4 @@
     // fontStyle: "italic",
     margin-left: -12;
     margin-top: 4;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 1b596ab65..418c518ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 
 .collectionfreeformview-none {
     position: inherit;
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 3c2f5ccd7..b8ceec139 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 @import '../../_nodeModuleOverrides';
 
 .collectionLinearView {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 76bd392a5..02131ae22 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.scss';
+@import '../../global/globalCssVariables.module.scss';
 
 .collectionSchemaView {
     cursor: default;
diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss
new file mode 100644
index 000000000..44e8efe23
--- /dev/null
+++ b/src/client/views/global/globalCssVariables.module.scss
@@ -0,0 +1,96 @@
+@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
+// colors
+$white: #ffffff;
+$off-white: #fdfdfd;
+$light-gray: #dfdfdf;
+$medium-gray: #9f9f9f;
+$medium-gray-dim: #9f9f9f30;
+$dark-gray: #323232;
+$black: #000000;
+
+$close-red: red;
+$minimize-yellow: yellow;
+$open-green: green;
+
+$light-blue: #bdddf5;
+$light-blue-transparent: #bdddf590;
+$medium-blue: #4476f7;
+$medium-blue-alt: #0047ff54;
+$pink: #e0217d;
+$yellow: #f5d747;
+
+$close-red: #e48282;
+
+$drop-shadow: '#32323215';
+
+$INK_MASK_SIZE: 50000;
+$INK_MASK_SIZE_HALF: -25000px;
+
+//padding
+$minimum-padding: 4px;
+$medium-padding: 16px;
+$large-padding: 32px;
+
+//icon sizes
+$icon-size: 28px;
+
+// fonts
+$sans-serif: 'Roboto', sans-serif;
+$large-header: 16px;
+$body-text: 13px;
+$small-text: 10px;
+// $sans-serif: "Roboto Slab", sans-serif;
+
+// misc values
+$border-radius: 0.3em;
+$search-thumnail-size: 130;
+$topbar-height: 37px;
+$antimodemenu-height: 36px;
+
+// dragged items
+$contextMenu-zindex: 100002; // context menu shows up over everything
+$radialMenu-zindex: 100000; // context menu shows up over everything
+
+// borders
+$standard-border: solid 1px #9f9f9f;
+
+// border radius
+$standard-border-radius: 3px;
+
+// shadow
+$standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+
+$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
+$docDecorations-zindex: 998; // then doc decorations appear over everything else
+$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
+$SCHEMA_DIVIDER_WIDTH: 4;
+$MINIMIZED_ICON_SIZE: 24;
+$MAX_ROW_HEIGHT: 44px;
+$DFLT_IMAGE_NATIVE_DIM: 900px;
+$LEFT_MENU_WIDTH: 60px;
+$TREE_BULLET_WIDTH: 20px;
+
+$CAROUSEL3D_CENTER_SCALE: 1.3;
+$CAROUSEL3D_SIDE_SCALE: 0.6;
+$CAROUSEL3D_TOP: 15;
+
+$DATA_VIZ_TABLE_ROW_HEIGHT: 30;
+
+:export {
+    contextMenuZindex: $contextMenu-zindex;
+    SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
+    MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+    MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
+    SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
+    ANTIMODEMENU_HEIGHT: $antimodemenu-height;
+    TOPBAR_HEIGHT: $topbar-height;
+    DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
+    LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
+    TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
+    INK_MASK_SIZE: $INK_MASK_SIZE;
+    MEDIUM_GRAY: $medium-gray;
+    CAROUSEL3D_CENTER_SCALE: $CAROUSEL3D_CENTER_SCALE;
+    CAROUSEL3D_SIDE_SCALE: $CAROUSEL3D_SIDE_SCALE;
+    CAROUSEL3D_TOP: $CAROUSEL3D_TOP;
+    DATA_VIZ_TABLE_ROW_HEIGHT: $DATA_VIZ_TABLE_ROW_HEIGHT;
+}
diff --git a/src/client/views/global/globalCssVariables.module.scss.d.ts b/src/client/views/global/globalCssVariables.module.scss.d.ts
new file mode 100644
index 000000000..bcbb1f068
--- /dev/null
+++ b/src/client/views/global/globalCssVariables.module.scss.d.ts
@@ -0,0 +1,21 @@
+interface IGlobalScss {
+    contextMenuZindex: string; // context menu shows up over everything
+    SCHEMA_DIVIDER_WIDTH: string;
+    MINIMIZED_ICON_SIZE: string;
+    MAX_ROW_HEIGHT: string;
+    SEARCH_THUMBNAIL_SIZE: string;
+    ANTIMODEMENU_HEIGHT: string;
+    TOPBAR_HEIGHT: string;
+    DFLT_IMAGE_NATIVE_DIM: string;
+    LEFT_MENU_WIDTH: string;
+    TREE_BULLET_WIDTH: string;
+    INK_MASK_SIZE: number;
+    MEDIUM_GRAY: string;
+    CAROUSEL3D_CENTER_SCALE: string;
+    CAROUSEL3D_SIDE_SCALE: string;
+    CAROUSEL3D_TOP: string;
+    DATA_VIZ_TABLE_ROW_HEIGHT: string;
+}
+declare const globalCssVariables: IGlobalScss;
+
+export = globalCssVariables;
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
deleted file mode 100644
index 44e8efe23..000000000
--- a/src/client/views/global/globalCssVariables.scss
+++ /dev/null
@@ -1,96 +0,0 @@
-@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
-// colors
-$white: #ffffff;
-$off-white: #fdfdfd;
-$light-gray: #dfdfdf;
-$medium-gray: #9f9f9f;
-$medium-gray-dim: #9f9f9f30;
-$dark-gray: #323232;
-$black: #000000;
-
-$close-red: red;
-$minimize-yellow: yellow;
-$open-green: green;
-
-$light-blue: #bdddf5;
-$light-blue-transparent: #bdddf590;
-$medium-blue: #4476f7;
-$medium-blue-alt: #0047ff54;
-$pink: #e0217d;
-$yellow: #f5d747;
-
-$close-red: #e48282;
-
-$drop-shadow: '#32323215';
-
-$INK_MASK_SIZE: 50000;
-$INK_MASK_SIZE_HALF: -25000px;
-
-//padding
-$minimum-padding: 4px;
-$medium-padding: 16px;
-$large-padding: 32px;
-
-//icon sizes
-$icon-size: 28px;
-
-// fonts
-$sans-serif: 'Roboto', sans-serif;
-$large-header: 16px;
-$body-text: 13px;
-$small-text: 10px;
-// $sans-serif: "Roboto Slab", sans-serif;
-
-// misc values
-$border-radius: 0.3em;
-$search-thumnail-size: 130;
-$topbar-height: 37px;
-$antimodemenu-height: 36px;
-
-// dragged items
-$contextMenu-zindex: 100002; // context menu shows up over everything
-$radialMenu-zindex: 100000; // context menu shows up over everything
-
-// borders
-$standard-border: solid 1px #9f9f9f;
-
-// border radius
-$standard-border-radius: 3px;
-
-// shadow
-$standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
-
-$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
-$docDecorations-zindex: 998; // then doc decorations appear over everything else
-$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
-$SCHEMA_DIVIDER_WIDTH: 4;
-$MINIMIZED_ICON_SIZE: 24;
-$MAX_ROW_HEIGHT: 44px;
-$DFLT_IMAGE_NATIVE_DIM: 900px;
-$LEFT_MENU_WIDTH: 60px;
-$TREE_BULLET_WIDTH: 20px;
-
-$CAROUSEL3D_CENTER_SCALE: 1.3;
-$CAROUSEL3D_SIDE_SCALE: 0.6;
-$CAROUSEL3D_TOP: 15;
-
-$DATA_VIZ_TABLE_ROW_HEIGHT: 30;
-
-:export {
-    contextMenuZindex: $contextMenu-zindex;
-    SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
-    MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
-    MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
-    SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
-    ANTIMODEMENU_HEIGHT: $antimodemenu-height;
-    TOPBAR_HEIGHT: $topbar-height;
-    DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
-    LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
-    TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
-    INK_MASK_SIZE: $INK_MASK_SIZE;
-    MEDIUM_GRAY: $medium-gray;
-    CAROUSEL3D_CENTER_SCALE: $CAROUSEL3D_CENTER_SCALE;
-    CAROUSEL3D_SIDE_SCALE: $CAROUSEL3D_SIDE_SCALE;
-    CAROUSEL3D_TOP: $CAROUSEL3D_TOP;
-    DATA_VIZ_TABLE_ROW_HEIGHT: $DATA_VIZ_TABLE_ROW_HEIGHT;
-}
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
deleted file mode 100644
index bcbb1f068..000000000
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-interface IGlobalScss {
-    contextMenuZindex: string; // context menu shows up over everything
-    SCHEMA_DIVIDER_WIDTH: string;
-    MINIMIZED_ICON_SIZE: string;
-    MAX_ROW_HEIGHT: string;
-    SEARCH_THUMBNAIL_SIZE: string;
-    ANTIMODEMENU_HEIGHT: string;
-    TOPBAR_HEIGHT: string;
-    DFLT_IMAGE_NATIVE_DIM: string;
-    LEFT_MENU_WIDTH: string;
-    TREE_BULLET_WIDTH: string;
-    INK_MASK_SIZE: number;
-    MEDIUM_GRAY: string;
-    CAROUSEL3D_CENTER_SCALE: string;
-    CAROUSEL3D_SIDE_SCALE: string;
-    CAROUSEL3D_TOP: string;
-    DATA_VIZ_TABLE_ROW_HEIGHT: string;
-}
-declare const globalCssVariables: IGlobalScss;
-
-export = globalCssVariables;
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 0b9f32eee..636b6415c 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .linkMenu {
     width: auto;
@@ -11,7 +11,9 @@
         display: inline-block;
         position: relative;
         border: 1px solid #e4e4e4;
-        box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+        box-shadow:
+            0 10px 20px rgba(0, 0, 0, 0.19),
+            0 6px 6px rgba(0, 0, 0, 0.23);
         max-height: 230px;
         overflow-y: scroll;
         z-index: 10;
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e83f631a1..44c74236f 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .linkMenu-item {
     // border-top: 0.5px solid $medium-gray;
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index d40537776..4337401e3 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
 
 .audiobox-container {
     width: 100%;
@@ -116,18 +116,18 @@
         width: 10px;
     }
 
-    input[type="range"] {
+    input[type='range'] {
         width: 50px;
         -webkit-appearance: none;
         background: none;
         margin: 5px;
     }
 
-    input[type="range"]:focus {
+    input[type='range']:focus {
         outline: none;
     }
 
-    input[type="range"]::-webkit-slider-runnable-track {
+    input[type='range']::-webkit-slider-runnable-track {
         width: 100%;
         height: 6px;
         cursor: pointer;
@@ -136,7 +136,7 @@
         border-radius: 3px;
     }
 
-    input[type="range"]::-webkit-slider-thumb {
+    input[type='range']::-webkit-slider-thumb {
         box-shadow: 0;
         border: 0;
         height: 10px;
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index c788a64c2..c0c0f10a2 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,4 +1,4 @@
-@import '../../../global/globalCssVariables';
+@import '../../../global/globalCssVariables.module.scss';
 .chart-container {
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 33be87f46..3e7d3af8c 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -11,9 +11,7 @@ import { DragManager } from '../../../../util/DragManager';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
-//import { DATA_VIZ_TABLE_ROW_HEIGHT } from '../../../global/globalCssVariables.scss';
-const DATA_VIZ_TABLE_ROW_HEIGHT = '30';
-
+const {  default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
 interface TableBoxProps {
     Document: Doc;
     layoutDoc: Doc;
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index 6da0b73ba..b32b27e65 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .documentLinksButton-wrapper {
     transform-origin: top left;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 406a1b8fb..c4dab16fb 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .documentView-effectsWrapper {
     border-radius: inherit;
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index f5871db22..5009ec7a7 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .equationBox-cont {
     transform-origin: center;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index 9d9fa26b0..db2ffa756 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 
 .menuButton {
     height: 100%;
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index ffcba4981..a44f614b2 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,7 +1,7 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 .keyValueBox-cont {
     overflow-y: scroll;
-    width:100%;
+    width: 100%;
     height: 100%;
     background-color: $white;
     border: 1px solid $medium-gray;
@@ -15,45 +15,45 @@
 }
 $header-height: 30px;
 .keyValueBox-tbody {
-    width:100%;
-    height:100%;
+    width: 100%;
+    height: 100%;
     position: absolute;
     overflow-y: scroll;
 }
 .keyValueBox-key {
     display: inline-block;
-    height:100%;
-    width:50%;
+    height: 100%;
+    width: 50%;
     text-align: center;
 }
 .keyValueBox-fields {
     display: inline-block;
-    height:100%;
-    width:50%;
+    height: 100%;
+    width: 50%;
     text-align: center;
 }
 
 .keyValueBox-table {
     position: absolute;
-    width:100%;
-    height:100%;
+    width: 100%;
+    height: 100%;
     border-collapse: collapse;
 }
 .keyValueBox-td-key {
-    display:inline-block;
-    height:30px;
+    display: inline-block;
+    height: 30px;
 }
 .keyValueBox-td-value {
-    display:inline-block;
-    height:30px;
+    display: inline-block;
+    height: 30px;
 }
 .keyValueBox-valueRow {
-    width:100%;
-    height:30px;
+    width: 100%;
+    height: 30px;
     display: inline-block;
 }
 .keyValueBox-header {
-    width:100%;
+    width: 100%;
     position: relative;
     display: inline-block;
     background: $medium-gray;
@@ -74,8 +74,8 @@ $header-height: 30px;
 .keyValueBox-evenRow {
     position: relative;
     display: flex;
-    width:100%;
-    height:$header-height;
+    width: 100%;
+    height: $header-height;
     background: $white;
     .formattedTextBox-cont {
         background: $white;
@@ -86,10 +86,10 @@ $header-height: 30px;
         position: relative;
     }
 }
-.keyValueBox-dividerDraggerThumb{
+.keyValueBox-dividerDraggerThumb {
     position: relative;
     width: 4px;
-    float: left; 
+    float: left;
     height: 30px;
     width: 5px;
     z-index: 20;
@@ -99,10 +99,10 @@ $header-height: 30px;
     background: black;
     pointer-events: all;
 }
-.keyValueBox-dividerDragger{
-    position: relative; 
+.keyValueBox-dividerDragger {
+    position: relative;
     width: 100%;
-    float: left; 
+    float: left;
     height: 37px;
     z-index: 20;
     right: 0;
@@ -114,10 +114,10 @@ $header-height: 30px;
 .keyValueBox-oddRow {
     position: relative;
     display: flex;
-    width:100%;
-    height:30px;
+    width: 100%;
+    height: 30px;
     background: $light-gray;
     .formattedTextBox-cont {
         background: $light-gray;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index c29af7817..46ea9c18e 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 .keyValuePair-td-key {
     display: inline-block;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 33c9b2e08..09ba0bc95 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -11,10 +11,11 @@ import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
-import { LinkDocPreview, LinkInfo } from './LinkDocPreview';
+import { LinkInfo } from './LinkDocPreview';
 import * as React from 'react';
-// import globalCssVariables = require('../global/globalCssVariables.scss');
-const MEDIUM_GRAY = 'lightGray';
+const {
+    default: { MEDIUM_GRAY },
+} = require('../global/globalCssVariables.module.scss');
 @observer
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index a8db5d360..104301656 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -1,7 +1,6 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
 
 .linkDescriptionPopup {
-
     display: flex;
     flex-direction: row;
     justify-content: center;
@@ -26,7 +25,6 @@
     }
 
     .linkDescriptionPopup-btn {
-
         float: right;
         justify-content: center;
         vertical-align: middle;
@@ -53,6 +51,4 @@
             color: black;
         }
     }
-
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 242677231..9b00c30cf 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.scss';
+@import '../../global/globalCssVariables.module.scss';
 .mapBox {
     width: 100%;
     height: 100%;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 8a68f9647..0f5e25a0c 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables.scss";
+@import '../global/globalCssVariables.module.scss';
 
 .pdfBox,
 .pdfBox-interactive {
@@ -38,7 +38,7 @@
             box-shadow: $standard-box-shadow;
             transition: 0.2s;
 
-            &:hover{
+            &:hover {
                 filter: brightness(0.85);
             }
         }
@@ -51,7 +51,8 @@
             left: 5px;
             top: 5px;
 
-            .pdfBox-fwdBtn, .pdfBox-backBtn {
+            .pdfBox-fwdBtn,
+            .pdfBox-backBtn {
                 background: #121721;
                 height: 25px;
                 width: 25px;
@@ -119,7 +120,6 @@
             background: none;
         }
 
-
         .pdfBox-settingsCont {
             position: absolute;
             right: 0;
@@ -194,7 +194,7 @@
             justify-content: center;
             align-items: center;
             overflow: hidden;
-            transition: left .5s;
+            transition: left 0.5s;
             pointer-events: all;
 
             .pdfBox-searchBar {
@@ -204,7 +204,6 @@
         }
     }
 
-    
     .pdfBox-title-outer {
         width: 100%;
         height: 100%;
@@ -269,7 +268,6 @@
 
 // CSS adjusted for mobile devices
 @media only screen and (max-device-width: 480px) {
-
     .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton,
     .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton {
         height: 60px;
@@ -288,15 +286,11 @@
         }
     }
 
-
-
     .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout,
     .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout {
         font-size: 30px;
     }
 
-
-
     .pdfBox .pdfBox-ui .pdfBox-overlayCont,
     .pdfBox-interactive .pdfBox-ui .pdfBox-overlayCont {
         height: 60px;
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
index 312b51013..9cd1ee23a 100644
--- a/src/client/views/nodes/RadialMenu.scss
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .radialMenu-cont {
     position: absolute;
@@ -26,7 +26,7 @@
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
+    transition: all 0.1s;
     border-style: none;
     white-space: nowrap;
     font-size: 13px;
@@ -34,8 +34,7 @@
     text-transform: uppercase;
 }
 
-s
-.radialMenu-itemSelected {
+s .radialMenu-itemSelected {
     border-style: none;
 }
 
@@ -50,8 +49,8 @@ s
     -moz-user-select: none;
     -ms-user-select: none;
     user-select: none;
-    transition: all .1s;
-    border-width: .11px;
+    transition: all 0.1s;
+    border-width: 0.11px;
     border-style: none;
     border-color: $medium-gray; //  rgb(187, 186, 186);
     // padding: 10px 0px 10px 0px;
@@ -62,9 +61,8 @@ s
     padding-left: 5px;
 }
 
-
 .radialMenu-description {
     margin-left: 5px;
     text-align: left;
     display: inline; //need this?
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f90c19050..ae923ad60 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .mini-viewer {
     cursor: grab;
@@ -172,7 +172,11 @@
         top: 90%;
         transform: translate(-50%, -50%);
         width: 80%;
-        transition: top 0s, width 0s, opacity 0.3s, visibility 0.3s;
+        transition:
+            top 0s,
+            width 0s,
+            opacity 0.3s,
+            visibility 0.3s;
     }
 }
 
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 511c91da0..a1686adaf 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.scss';
+@import '../global/globalCssVariables.module.scss';
 
 .webBox {
     height: 100%;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index ad315acc8..3426ba1a7 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 
 .dashFieldView {
     position: relative;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 717efd5a9..03ff0436b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 
 .ProseMirror {
     width: 100%;
@@ -592,7 +592,7 @@ footnote::before {
 }
 
 @media only screen and (max-width: 1000px) {
-    @import '../../global/globalCssVariables';
+    @import '../../global/globalCssVariables.module.scss';
 
     .ProseMirror {
         width: 100%;
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index 8afa0f6b5..d6ed5ebee 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -1,4 +1,4 @@
-@import "../../global/globalCssVariables";
+@import '../../global/globalCssVariables.module.scss';
 
 .button-dropdown-wrapper {
     position: relative;
@@ -55,7 +55,6 @@
     input {
         color: black;
     }
-
 }
 
 .richTextMenu {
@@ -68,12 +67,12 @@
         font-size: 12px;
         height: 100%;
         margin-right: 3px;
-    
+
         &:focus,
         &:hover {
             background-color: black;
         }
-    
+
         &::-ms-expand {
             color: white;
         }
@@ -126,7 +125,7 @@
     display: flex;
     justify-content: space-between;
 
-    >div {
+    > div {
         display: flex;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/TooltipTextMenu.scss b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
index 8c4d77da9..87320943d 100644
--- a/src/client/views/nodes/formattedText/TooltipTextMenu.scss
+++ b/src/client/views/nodes/formattedText/TooltipTextMenu.scss
@@ -1,9 +1,9 @@
-@import "../views/global/globalCssVariables";
+@import '../views/global/globalCssVariables.module.scss';
 .ProseMirror-menu-dropdown-wrap {
     display: inline-block;
     position: relative;
 }
-  
+
 .ProseMirror-menu-dropdown {
     vertical-align: 1px;
     cursor: pointer;
@@ -17,11 +17,11 @@
     margin-right: 4px;
 
     &:after {
-        content: "";
+        content: '';
         border-left: 4px solid transparent;
         border-right: 4px solid transparent;
         border-top: 4px solid currentColor;
-        opacity: .6;
+        opacity: 0.6;
         position: absolute;
         right: 4px;
         top: calc(50% - 2px);
@@ -33,7 +33,7 @@
     margin-right: -4px;
 }
 
-.ProseMirror-menu-dropdown-menu, 
+.ProseMirror-menu-dropdown-menu,
 .ProseMirror-menu-submenu {
     font-size: 12px;
     background: white;
@@ -55,19 +55,18 @@
     }
 }
 
-
 .ProseMirror-menu-submenu-label:after {
-    content: "";
+    content: '';
     border-top: 4px solid transparent;
     border-bottom: 4px solid transparent;
     border-left: 4px solid currentColor;
-    opacity: .6;
+    opacity: 0.6;
     position: absolute;
     right: 4px;
     top: calc(50% - 4px);
 }
-  
-  .ProseMirror-icon {
+
+.ProseMirror-icon {
     display: inline-block;
     // line-height: .8;
     // vertical-align: -2px; /* Compensate for padding */
@@ -79,14 +78,14 @@
     }
 
     svg {
-        fill:white;
+        fill: white;
         height: 1em;
     }
 
     span {
         vertical-align: text-top;
-      }  
-  }
+    }
+}
 
 .wrapper {
     position: absolute;
@@ -99,10 +98,10 @@
     background: #323232;
     border-radius: 6px;
     box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
-
 }
 
-.tooltipMenu, .basic-tools {
+.tooltipMenu,
+.basic-tools {
     z-index: 3000;
     pointer-events: all;
     padding: 3px;
@@ -111,17 +110,17 @@
     align-items: center;
 
     .ProseMirror-example-setup-style hr {
-      padding: 2px 10px;
-      border: none;
-      margin: 1em 0;
+        padding: 2px 10px;
+        border: none;
+        margin: 1em 0;
     }
-    
+
     .ProseMirror-example-setup-style hr:after {
-      content: "";
-      display: block;
-      height: 1px;
-      background-color: silver;
-      line-height: 2px;
+        content: '';
+        display: block;
+        height: 1px;
+        background-color: silver;
+        line-height: 2px;
     }
 }
 
@@ -142,7 +141,7 @@
         }
     }
 
-    &> * {
+    & > * {
         margin-top: 50%;
         margin-left: 50%;
         transform: translate(-50%, -50%);
@@ -168,7 +167,7 @@
         background-color: black;
     }
 
-    &> * {
+    & > * {
         margin-top: 50%;
         margin-left: 50%;
         transform: translate(-50%, -50%);
@@ -208,18 +207,17 @@
     margin-top: 13px;
 }
 
-  .font-size-indicator {
-      font-size: 12px;
-      padding-right: 0px;
-  }
-  .summarize{
-      color: white;
-      height: 20px;
-      text-align: center;
-  }
-  
-
-.brush{
+.font-size-indicator {
+    font-size: 12px;
+    padding-right: 0px;
+}
+.summarize {
+    color: white;
+    height: 20px;
+    text-align: center;
+}
+
+.brush {
     display: inline-block;
     width: 1em;
     height: 1em;
@@ -229,7 +227,7 @@
     margin-right: 15px;
 }
 
-.brush-active{
+.brush-active {
     display: inline-block;
     width: 1em;
     height: 1em;
@@ -269,7 +267,6 @@
 }
 
 .buttonSettings-dropdown {
-
     &.ProseMirror-menu-dropdown {
         width: 10px;
         height: 25px;
@@ -301,7 +298,7 @@
         padding: 3px;
         box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
 
-        .ProseMirror-menu-dropdown-item{
+        .ProseMirror-menu-dropdown-item {
             cursor: default;
 
             &:last-child {
@@ -312,7 +309,8 @@
                 background-color: #323232;
             }
 
-            .button-setting, .button-setting-disabled {
+            .button-setting,
+            .button-setting-disabled {
                 padding: 2px;
                 border-radius: 2px;
             }
@@ -328,25 +326,23 @@
             }
 
             input {
-               color: black;
-               border: none;
-               border-radius: 1px;
-               padding: 3px;
+                color: black;
+                border: none;
+                border-radius: 1px;
+                padding: 3px;
             }
 
             button {
-               padding: 6px;
-               background-color: #323232;
-               border: 1px solid black;
-               border-radius: 1px;
+                padding: 6px;
+                background-color: #323232;
+                border: 1px solid black;
+                border-radius: 1px;
 
                 &:hover {
-                   background-color: black;
+                    background-color: black;
                 }
             }
         }
-
-
     }
 }
 
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 0b51813a5..3b34a1f90 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables';
+@import '../../global/globalCssVariables.module.scss';
 
 .presBox-cont {
     cursor: auto;
diff --git a/src/client/views/search/CheckBox.scss b/src/client/views/search/CheckBox.scss
index 2a0085ade..4892facbc 100644
--- a/src/client/views/search/CheckBox.scss
+++ b/src/client/views/search/CheckBox.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .checkboxfilter {
     display: flex;
@@ -12,7 +12,7 @@
         align-items: center;
         margin-top: 0px;
 
-        .check-container:hover~.check-box {
+        .check-container:hover ~ .check-box {
             background-color: $medium-blue;
         }
 
@@ -55,5 +55,4 @@
         text-transform: capitalize;
         margin-left: 15px;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/CollectionFilters.scss b/src/client/views/search/CollectionFilters.scss
index 845b16f67..6105df570 100644
--- a/src/client/views/search/CollectionFilters.scss
+++ b/src/client/views/search/CollectionFilters.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .collection-filters {
     display: flex;
@@ -17,4 +17,4 @@
         float: right;
         opacity: 0;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
index 6aaf7918d..05aa099f7 100644
--- a/src/client/views/search/IconBar.scss
+++ b/src/client/views/search/IconBar.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .icon-bar {
     display: flex;
@@ -7,4 +7,4 @@
     height: auto;
     width: 100%;
     flex-direction: row-reverse;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss
index 3cb08d756..d2a2ea369 100644
--- a/src/client/views/search/IconButton.scss
+++ b/src/client/views/search/IconButton.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .type-outer {
     display: flex;
@@ -24,7 +24,7 @@
 
         .fontawesome-icon {
             height: 15px;
-            width: 15px
+            width: 15px;
         }
     }
 
@@ -46,8 +46,8 @@
         background-color: $medium-blue;
         opacity: 1;
 
-        +.filter-description {
+        + .filter-description {
             opacity: 1;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index e12e4511c..20084b64d 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -4,7 +4,7 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DocumentType } from '../../documents/DocumentTypes';
-import '../global/globalCssVariables.scss';
+import '../global/globalCssVariables.module.scss';
 import { IconBar } from './IconBar';
 import './IconButton.scss';
 import './SearchBox.scss';
diff --git a/src/client/views/search/NaviconButton.scss b/src/client/views/search/NaviconButton.scss
index 13d31bbe9..917635c0b 100644
--- a/src/client/views/search/NaviconButton.scss
+++ b/src/client/views/search/NaviconButton.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables';
+@import '../global/globalCssVariables.module.scss';
 
 $height-icon: 15px;
 $width-line: 30px;
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index a439aea3e..a3c4bd2ed 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -1,5 +1,5 @@
-@import "../global/globalCssVariables";
-@import "./NaviconButton.scss";
+@import '../global/globalCssVariables.module.scss';
+@import './NaviconButton.scss';
 
 .searchBox-container {
     width: 100%;
@@ -47,7 +47,6 @@
     }
 
     .section-header {
-
         .section-title {
             font-size: $body-text;
             font-weight: 600;
@@ -57,7 +56,7 @@
             display: flex;
             color: $light-gray;
         }
-        
+
         padding: 5px 10px;
         display: flex;
         flex-direction: column;
@@ -71,7 +70,7 @@
         flex-direction: column;
         width: 100%;
         height: fit-content;
-        justify-content: "center";
+        justify-content: 'center';
 
         .searchBox-recommendations-view {
             margin-top: 10px;
@@ -81,8 +80,6 @@
             flex-direction: column;
             gap: 10px;
             padding: 0px 10px;
-            
-
         }
     }
 
@@ -91,8 +88,8 @@
         flex-direction: column;
         width: 100%;
         height: fit-content;
-        justify-content: "center";
-    
+        justify-content: 'center';
+
         .searchBox-results-view {
             display: inline-block;
             width: 100%;
@@ -147,4 +144,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/SelectorContextMenu.scss b/src/client/views/search/SelectorContextMenu.scss
index a114f679c..097a6107d 100644
--- a/src/client/views/search/SelectorContextMenu.scss
+++ b/src/client/views/search/SelectorContextMenu.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .parents {
     background: $light-blue;
@@ -13,4 +13,4 @@
         border-color: $medium-blue;
         border-bottom-style: solid;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/search/ToggleBar.scss b/src/client/views/search/ToggleBar.scss
index 3a164f133..9e27db2bc 100644
--- a/src/client/views/search/ToggleBar.scss
+++ b/src/client/views/search/ToggleBar.scss
@@ -1,4 +1,4 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .toggle-title {
     display: flex;
@@ -38,4 +38,4 @@
         border-radius: 10px;
         background-color: $white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 2237d5ac1..20245104e 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,5 +1,4 @@
-@import '../global/globalCssVariables';
-
+@import '../global/globalCssVariables.module.scss';
 
 .topbar-container {
     flex-direction: column;
diff --git a/src/client/views/webcam/DashWebRTCVideo.scss b/src/client/views/webcam/DashWebRTCVideo.scss
index 249aee9d6..5744ebbcd 100644
--- a/src/client/views/webcam/DashWebRTCVideo.scss
+++ b/src/client/views/webcam/DashWebRTCVideo.scss
@@ -1,11 +1,11 @@
-@import "../global/globalCssVariables";
+@import '../global/globalCssVariables.module.scss';
 
 .webcam-cont {
     background: whitesmoke;
     color: grey;
     border-radius: 15px;
     box-shadow: #9c9396 0.2vw 0.2vw 0.4vw;
-    border: solid #BBBBBBBB 5px;
+    border: solid #bbbbbbbb 5px;
     pointer-events: all;
     display: flex;
     flex-direction: column;
@@ -44,7 +44,7 @@
     #roomName {
         outline: none;
         border-radius: inherit;
-        border: 1px solid #BBBBBBBB;
+        border: 1px solid #bbbbbbbb;
         margin: 10px;
         padding: 10px;
     }
@@ -79,5 +79,4 @@
         margin: 5px;
         border: 1px solid black;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss
index 6669a3d21..e4156ee8e 100644
--- a/src/mobile/ImageUpload.scss
+++ b/src/mobile/ImageUpload.scss
@@ -1,4 +1,4 @@
-@import "../client/views/global/globalCssVariables.scss";
+@import '../client/views/global/globalCssVariables.module.scss';
 
 .imgupload_cont {
     display: flex;
@@ -50,7 +50,7 @@
         z-index: -1;
     }
 
-    .inputfile+label {
+    .inputfile + label {
         font-weight: 700;
         color: black;
         background-color: rgba(0, 0, 0, 0);
@@ -71,7 +71,7 @@
         border-radius: 10px;
     }
 
-    .inputfile.active+label {
+    .inputfile.active + label {
         font-style: italic;
         color: black;
         background-color: lightgreen;
@@ -81,7 +81,6 @@
     .status {
         font-size: 2em;
     }
-
 }
 
 .image-upload {
@@ -134,5 +133,7 @@
     border-radius: 20px;
     opacity: 0.2;
     background-color: black;
-    transition: all 2s, opacity 1.5s;
-}
\ No newline at end of file
+    transition:
+        all 2s,
+        opacity 1.5s;
+}
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ee212e4af..d2598c2db 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -14,8 +14,7 @@ import { Utils } from '../Utils';
 import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
 import * as React from 'react';
-//import { DFLT_IMAGE_NATIVE_DIM } from '../client/views/global/globalCssVariables.scss';
-const DFLT_IMAGE_NATIVE_DIM = '50px';
+const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
 }
diff --git a/webpack.config.js b/webpack.config.js
index c1464ac2a..a153616b8 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -12,8 +12,8 @@ const plugins = [
     // new ForkTsCheckerWebpackPlugin({
     //     typescript: {
     //         // useTypescriptIncrementalApi: true,
-    //         // tslint: true,
-    //         memoryLimit: 4096,
+    //         eslint: true,
+    //         memoryLimit: 8192,
     //     },
     //     // tslint: true,
     //     // memoryLimit: 4096,
@@ -108,16 +108,22 @@ module.exports = {
             },
             {
                 test: /\.scss|css$/,
+                exclude: /\.module\.scss$/i,
                 use: [
-                    {
-                        loader: 'style-loader',
-                    },
-                    {
-                        loader: 'css-loader',
-                    },
-                    {
-                        loader: 'sass-loader',
-                    },
+                    { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier
+                    { loader: 'css-loader' },
+                    { loader: 'sass-loader' },
+                ],
+            },
+
+            // --------
+            // SCSS MODULES - all have .module. in their name and can export to .tsx
+            {
+                test: /\.module\.scss$/i,
+                use: [
+                    { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier
+                    { loader: 'css-loader', options: { modules: true } },
+                    { loader: 'sass-loader' },
                 ],
             },
             {
-- 
cgit v1.2.3-70-g09d2


From adf56d455ab0e429b7eac3430890ba7677cce8d9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 15 Dec 2023 11:16:37 -0500
Subject: more fixes for mapbox and dataviz

---
 package-lock.json                                  | 2188 +++++++++++++++++++-
 package.json                                       |    3 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   14 +-
 .../nodes/DataVizBox/components/Histogram.tsx      |  102 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |   66 +-
 .../views/nodes/DataVizBox/components/PieChart.tsx |   88 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   79 +-
 src/client/views/nodes/MapBox/AnimationUtility.ts  |  482 ++---
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |   69 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   66 +-
 webpack.config.js                                  |    2 +-
 11 files changed, 2636 insertions(+), 523 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index 18c4f3d05..6496b4184 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
                 "@mui/material": "^5.14.19",
                 "@octokit/core": "^5.0.2",
                 "@react-google-maps/api": "^2.19.2",
+                "@turf/turf": "^6.5.0",
                 "@types/bezier-js": "^4.1.3",
                 "@types/cors": "^2.8.17",
                 "@types/d3-axis": "^3.0.6",
@@ -113,6 +114,7 @@
                 "jsonschema": "^1.4.1",
                 "jszip": "^3.10.1",
                 "lodash": "^4.17.21",
+                "mapbox-gl": "^3.0.1",
                 "mathquill": "^0.10.1-a",
                 "md5-file": "^5.0.0",
                 "memorystream": "^0.3.1",
@@ -163,6 +165,7 @@
                 "react-icons": "^4.12.0",
                 "react-jsx-parser": "^1.29.0",
                 "react-loading": "^2.0.3",
+                "react-map-gl": "^7.1.6",
                 "react-markdown": "^9.0.1",
                 "react-measure": "^2.5.2",
                 "react-resizable": "^3.0.5",
@@ -3126,6 +3129,42 @@
             "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
             "dev": true
         },
+        "node_modules/@mapbox/geojson-rewind": {
+            "version": "0.5.2",
+            "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz",
+            "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==",
+            "dependencies": {
+                "get-stream": "^6.0.1",
+                "minimist": "^1.2.6"
+            },
+            "bin": {
+                "geojson-rewind": "geojson-rewind"
+            }
+        },
+        "node_modules/@mapbox/geojson-rewind/node_modules/get-stream": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+            "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@mapbox/jsonlint-lines-primitives": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
+            "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/@mapbox/mapbox-gl-supported": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz",
+            "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ=="
+        },
         "node_modules/@mapbox/node-pre-gyp": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
@@ -3228,6 +3267,55 @@
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "node_modules/@mapbox/point-geometry": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
+            "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
+        },
+        "node_modules/@mapbox/tiny-sdf": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz",
+            "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="
+        },
+        "node_modules/@mapbox/unitbezier": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz",
+            "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
+        },
+        "node_modules/@mapbox/vector-tile": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz",
+            "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==",
+            "dependencies": {
+                "@mapbox/point-geometry": "~0.1.0"
+            }
+        },
+        "node_modules/@mapbox/whoots-js": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
+            "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==",
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@maplibre/maplibre-gl-style-spec": {
+            "version": "19.3.3",
+            "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz",
+            "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==",
+            "dependencies": {
+                "@mapbox/jsonlint-lines-primitives": "~2.0.2",
+                "@mapbox/unitbezier": "^0.0.1",
+                "json-stringify-pretty-compact": "^3.0.0",
+                "minimist": "^1.2.8",
+                "rw": "^1.3.3",
+                "sort-object": "^3.0.3"
+            },
+            "bin": {
+                "gl-style-format": "dist/gl-style-format.mjs",
+                "gl-style-migrate": "dist/gl-style-migrate.mjs",
+                "gl-style-validate": "dist/gl-style-validate.mjs"
+            }
+        },
         "node_modules/@mongodb-js/saslprep": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz",
@@ -3775,11 +3863,1588 @@
             "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
             "dev": true
         },
-        "node_modules/@tsconfig/node16": {
-            "version": "1.0.4",
-            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
-            "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
-            "dev": true
+        "node_modules/@tsconfig/node16": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+            "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+            "dev": true
+        },
+        "node_modules/@turf/along": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz",
+            "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/angle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz",
+            "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/area": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz",
+            "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/bbox": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz",
+            "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/bbox-clip": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz",
+            "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/bbox-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz",
+            "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/bearing": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz",
+            "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/bezier-spline": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz",
+            "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-clockwise": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz",
+            "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-contains": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz",
+            "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-crosses": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz",
+            "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-disjoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz",
+            "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-equal": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz",
+            "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==",
+            "dependencies": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "geojson-equality": "0.1.6"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-intersects": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz",
+            "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==",
+            "dependencies": {
+                "@turf/boolean-disjoint": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-overlap": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz",
+            "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-overlap": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "geojson-equality": "0.1.6"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-parallel": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz",
+            "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==",
+            "dependencies": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-point-in-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz",
+            "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-point-on-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz",
+            "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/boolean-within": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz",
+            "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/buffer": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz",
+            "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "d3-geo": "1.7.1",
+                "turf-jsts": "*"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/buffer/node_modules/d3-array": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
+            "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
+        },
+        "node_modules/@turf/buffer/node_modules/d3-geo": {
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz",
+            "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==",
+            "dependencies": {
+                "d3-array": "1"
+            }
+        },
+        "node_modules/@turf/center": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz",
+            "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/center-mean": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz",
+            "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/center-median": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz",
+            "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==",
+            "dependencies": {
+                "@turf/center-mean": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/center-of-mass": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz",
+            "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==",
+            "dependencies": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/convex": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/centroid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz",
+            "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/circle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz",
+            "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==",
+            "dependencies": {
+                "@turf/destination": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/clean-coords": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz",
+            "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/clone": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz",
+            "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/clusters": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz",
+            "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/clusters-dbscan": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz",
+            "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "density-clustering": "1.3.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/clusters-kmeans": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz",
+            "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "skmeans": "0.9.7"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/collect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz",
+            "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "rbush": "2.x"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/combine": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz",
+            "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/concave": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz",
+            "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/tin": "^6.5.0",
+                "topojson-client": "3.x",
+                "topojson-server": "3.x"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/convex": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz",
+            "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "concaveman": "*"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/destination": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz",
+            "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/difference": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz",
+            "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/dissolve": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz",
+            "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz",
+            "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/distance-weight": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz",
+            "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==",
+            "dependencies": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/ellipse": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz",
+            "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/transform-rotate": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/envelope": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz",
+            "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/explode": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz",
+            "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/flatten": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz",
+            "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/flip": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz",
+            "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/great-circle": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz",
+            "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/helpers": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz",
+            "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==",
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/hex-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz",
+            "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==",
+            "dependencies": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/intersect": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/interpolate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz",
+            "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/hex-grid": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/point-grid": "^6.5.0",
+                "@turf/square-grid": "^6.5.0",
+                "@turf/triangle-grid": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/intersect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz",
+            "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/invariant": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz",
+            "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/isobands": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz",
+            "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==",
+            "dependencies": {
+                "@turf/area": "^6.5.0",
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "object-assign": "*"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/isolines": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz",
+            "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "object-assign": "*"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/kinks": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz",
+            "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/length": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz",
+            "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==",
+            "dependencies": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-arc": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz",
+            "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==",
+            "dependencies": {
+                "@turf/circle": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-chunk": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz",
+            "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/length": "^6.5.0",
+                "@turf/line-slice-along": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-intersect": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz",
+            "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "geojson-rbush": "3.x"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-offset": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz",
+            "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-overlap": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz",
+            "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==",
+            "dependencies": {
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "deep-equal": "1.x",
+                "geojson-rbush": "3.x"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-segment": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz",
+            "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-slice": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz",
+            "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-slice-along": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz",
+            "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-split": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz",
+            "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "@turf/square": "^6.5.0",
+                "@turf/truncate": "^6.5.0",
+                "geojson-rbush": "3.x"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/line-to-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz",
+            "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/mask": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz",
+            "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/meta": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz",
+            "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/midpoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz",
+            "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/moran-index": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz",
+            "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==",
+            "dependencies": {
+                "@turf/distance-weight": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/nearest-point": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz",
+            "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/nearest-point-on-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz",
+            "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/nearest-point-to-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz",
+            "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/point-to-line-distance": "^6.5.0",
+                "object-assign": "*"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/planepoint": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz",
+            "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/point-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz",
+            "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==",
+            "dependencies": {
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/point-on-feature": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz",
+            "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/point-to-line-distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz",
+            "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==",
+            "dependencies": {
+                "@turf/bearing": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/points-within-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz",
+            "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/polygon-smooth": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz",
+            "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/polygon-tangents": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz",
+            "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/polygon-to-line": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz",
+            "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/polygonize": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz",
+            "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/envelope": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/projection": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz",
+            "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/random": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz",
+            "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/rectangle-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz",
+            "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==",
+            "dependencies": {
+                "@turf/boolean-intersects": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/rewind": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz",
+            "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==",
+            "dependencies": {
+                "@turf/boolean-clockwise": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/rhumb-bearing": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz",
+            "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/rhumb-destination": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz",
+            "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/rhumb-distance": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz",
+            "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/sample": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz",
+            "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/sector": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz",
+            "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==",
+            "dependencies": {
+                "@turf/circle": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/line-arc": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/shortest-path": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz",
+            "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/transform-scale": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/simplify": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz",
+            "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==",
+            "dependencies": {
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/square": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz",
+            "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==",
+            "dependencies": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/square-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz",
+            "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/rectangle-grid": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/standard-deviational-ellipse": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz",
+            "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==",
+            "dependencies": {
+                "@turf/center-mean": "^6.5.0",
+                "@turf/ellipse": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/points-within-polygon": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/tag": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz",
+            "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==",
+            "dependencies": {
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/tesselate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz",
+            "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "earcut": "^2.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/tin": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz",
+            "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/transform-rotate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz",
+            "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==",
+            "dependencies": {
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/transform-scale": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz",
+            "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==",
+            "dependencies": {
+                "@turf/bbox": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/transform-translate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz",
+            "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==",
+            "dependencies": {
+                "@turf/clone": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/triangle-grid": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz",
+            "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==",
+            "dependencies": {
+                "@turf/distance": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/intersect": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/truncate": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz",
+            "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/turf": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz",
+            "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==",
+            "dependencies": {
+                "@turf/along": "^6.5.0",
+                "@turf/angle": "^6.5.0",
+                "@turf/area": "^6.5.0",
+                "@turf/bbox": "^6.5.0",
+                "@turf/bbox-clip": "^6.5.0",
+                "@turf/bbox-polygon": "^6.5.0",
+                "@turf/bearing": "^6.5.0",
+                "@turf/bezier-spline": "^6.5.0",
+                "@turf/boolean-clockwise": "^6.5.0",
+                "@turf/boolean-contains": "^6.5.0",
+                "@turf/boolean-crosses": "^6.5.0",
+                "@turf/boolean-disjoint": "^6.5.0",
+                "@turf/boolean-equal": "^6.5.0",
+                "@turf/boolean-intersects": "^6.5.0",
+                "@turf/boolean-overlap": "^6.5.0",
+                "@turf/boolean-parallel": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/boolean-point-on-line": "^6.5.0",
+                "@turf/boolean-within": "^6.5.0",
+                "@turf/buffer": "^6.5.0",
+                "@turf/center": "^6.5.0",
+                "@turf/center-mean": "^6.5.0",
+                "@turf/center-median": "^6.5.0",
+                "@turf/center-of-mass": "^6.5.0",
+                "@turf/centroid": "^6.5.0",
+                "@turf/circle": "^6.5.0",
+                "@turf/clean-coords": "^6.5.0",
+                "@turf/clone": "^6.5.0",
+                "@turf/clusters": "^6.5.0",
+                "@turf/clusters-dbscan": "^6.5.0",
+                "@turf/clusters-kmeans": "^6.5.0",
+                "@turf/collect": "^6.5.0",
+                "@turf/combine": "^6.5.0",
+                "@turf/concave": "^6.5.0",
+                "@turf/convex": "^6.5.0",
+                "@turf/destination": "^6.5.0",
+                "@turf/difference": "^6.5.0",
+                "@turf/dissolve": "^6.5.0",
+                "@turf/distance": "^6.5.0",
+                "@turf/distance-weight": "^6.5.0",
+                "@turf/ellipse": "^6.5.0",
+                "@turf/envelope": "^6.5.0",
+                "@turf/explode": "^6.5.0",
+                "@turf/flatten": "^6.5.0",
+                "@turf/flip": "^6.5.0",
+                "@turf/great-circle": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/hex-grid": "^6.5.0",
+                "@turf/interpolate": "^6.5.0",
+                "@turf/intersect": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "@turf/isobands": "^6.5.0",
+                "@turf/isolines": "^6.5.0",
+                "@turf/kinks": "^6.5.0",
+                "@turf/length": "^6.5.0",
+                "@turf/line-arc": "^6.5.0",
+                "@turf/line-chunk": "^6.5.0",
+                "@turf/line-intersect": "^6.5.0",
+                "@turf/line-offset": "^6.5.0",
+                "@turf/line-overlap": "^6.5.0",
+                "@turf/line-segment": "^6.5.0",
+                "@turf/line-slice": "^6.5.0",
+                "@turf/line-slice-along": "^6.5.0",
+                "@turf/line-split": "^6.5.0",
+                "@turf/line-to-polygon": "^6.5.0",
+                "@turf/mask": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "@turf/midpoint": "^6.5.0",
+                "@turf/moran-index": "^6.5.0",
+                "@turf/nearest-point": "^6.5.0",
+                "@turf/nearest-point-on-line": "^6.5.0",
+                "@turf/nearest-point-to-line": "^6.5.0",
+                "@turf/planepoint": "^6.5.0",
+                "@turf/point-grid": "^6.5.0",
+                "@turf/point-on-feature": "^6.5.0",
+                "@turf/point-to-line-distance": "^6.5.0",
+                "@turf/points-within-polygon": "^6.5.0",
+                "@turf/polygon-smooth": "^6.5.0",
+                "@turf/polygon-tangents": "^6.5.0",
+                "@turf/polygon-to-line": "^6.5.0",
+                "@turf/polygonize": "^6.5.0",
+                "@turf/projection": "^6.5.0",
+                "@turf/random": "^6.5.0",
+                "@turf/rewind": "^6.5.0",
+                "@turf/rhumb-bearing": "^6.5.0",
+                "@turf/rhumb-destination": "^6.5.0",
+                "@turf/rhumb-distance": "^6.5.0",
+                "@turf/sample": "^6.5.0",
+                "@turf/sector": "^6.5.0",
+                "@turf/shortest-path": "^6.5.0",
+                "@turf/simplify": "^6.5.0",
+                "@turf/square": "^6.5.0",
+                "@turf/square-grid": "^6.5.0",
+                "@turf/standard-deviational-ellipse": "^6.5.0",
+                "@turf/tag": "^6.5.0",
+                "@turf/tesselate": "^6.5.0",
+                "@turf/tin": "^6.5.0",
+                "@turf/transform-rotate": "^6.5.0",
+                "@turf/transform-scale": "^6.5.0",
+                "@turf/transform-translate": "^6.5.0",
+                "@turf/triangle-grid": "^6.5.0",
+                "@turf/truncate": "^6.5.0",
+                "@turf/union": "^6.5.0",
+                "@turf/unkink-polygon": "^6.5.0",
+                "@turf/voronoi": "^6.5.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/union": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz",
+            "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "polygon-clipping": "^0.15.3"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/unkink-polygon": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz",
+            "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==",
+            "dependencies": {
+                "@turf/area": "^6.5.0",
+                "@turf/boolean-point-in-polygon": "^6.5.0",
+                "@turf/helpers": "^6.5.0",
+                "@turf/meta": "^6.5.0",
+                "rbush": "^2.0.1"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
+        },
+        "node_modules/@turf/voronoi": {
+            "version": "6.5.0",
+            "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz",
+            "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==",
+            "dependencies": {
+                "@turf/helpers": "^6.5.0",
+                "@turf/invariant": "^6.5.0",
+                "d3-voronoi": "1.1.2"
+            },
+            "funding": {
+                "url": "https://opencollective.com/turf"
+            }
         },
         "node_modules/@types/adm-zip": {
             "version": "0.5.5",
@@ -4415,6 +6080,14 @@
             "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
             "dev": true
         },
+        "node_modules/@types/mapbox-gl": {
+            "version": "2.7.19",
+            "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.19.tgz",
+            "integrity": "sha512-pkRdlhQJNbtwcKJSVC4uuOA6M3OlOObIMhNecqHB991oeaDF5XkjSIRaTE0lh5p4KClptSCxW6MBiREVRHrl2A==",
+            "dependencies": {
+                "@types/geojson": "*"
+            }
+        },
         "node_modules/@types/mdast": {
             "version": "4.0.3",
             "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz",
@@ -5496,6 +7169,14 @@
                 "dequal": "^2.0.3"
             }
         },
+        "node_modules/arr-union": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+            "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/array-back": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
@@ -5703,6 +7384,14 @@
                 "node": "*"
             }
         },
+        "node_modules/assign-symbols": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+            "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/ast-types-flow": {
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -9255,6 +10944,23 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/bytewise": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz",
+            "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==",
+            "dependencies": {
+                "bytewise-core": "^1.2.2",
+                "typewise": "^1.0.3"
+            }
+        },
+        "node_modules/bytewise-core": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz",
+            "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==",
+            "dependencies": {
+                "typewise-core": "^1.2"
+            }
+        },
         "node_modules/cacheable-lookup": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
@@ -9503,6 +11209,11 @@
                 "pnpm": ">=7"
             }
         },
+        "node_modules/cheap-ruler": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz",
+            "integrity": "sha512-02T332h1/HTN6cDSufLP8x4JzDs2+VC+8qZ/N0kWIVPyc2xUkWwWh3B2fJxR7raXkL4Mq7k554mfuM9ofv/vGg=="
+        },
         "node_modules/check-error": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
@@ -9920,6 +11631,35 @@
             "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
             "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
         },
+        "node_modules/concaveman": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz",
+            "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==",
+            "dependencies": {
+                "point-in-polygon": "^1.1.0",
+                "rbush": "^3.0.1",
+                "robust-predicates": "^2.0.4",
+                "tinyqueue": "^2.0.3"
+            }
+        },
+        "node_modules/concaveman/node_modules/quickselect": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+            "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+        },
+        "node_modules/concaveman/node_modules/rbush": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
+            "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
+            "dependencies": {
+                "quickselect": "^2.0.0"
+            }
+        },
+        "node_modules/concaveman/node_modules/robust-predicates": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz",
+            "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg=="
+        },
         "node_modules/confusing-browser-globals": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@@ -10344,6 +12084,11 @@
                 "url": "https://github.com/sponsors/fb55"
             }
         },
+        "node_modules/csscolorparser": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
+            "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w=="
+        },
         "node_modules/cssesc": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -10768,6 +12513,11 @@
                 "d3-selection": "2 - 3"
             }
         },
+        "node_modules/d3-voronoi": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz",
+            "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw=="
+        },
         "node_modules/d3-zoom": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
@@ -10932,6 +12682,25 @@
                 "node": ">=6"
             }
         },
+        "node_modules/deep-equal": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
+            "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
+            "dependencies": {
+                "is-arguments": "^1.1.1",
+                "is-date-object": "^1.0.5",
+                "is-regex": "^1.1.4",
+                "object-is": "^1.1.5",
+                "object-keys": "^1.1.1",
+                "regexp.prototype.flags": "^1.5.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/deep-extend": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -11126,7 +12895,6 @@
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
             "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
-            "dev": true,
             "dependencies": {
                 "define-data-property": "^1.0.1",
                 "has-property-descriptors": "^1.0.0",
@@ -11160,6 +12928,11 @@
             "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
             "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
         },
+        "node_modules/density-clustering": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz",
+            "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ=="
+        },
         "node_modules/depcheck": {
             "version": "1.4.7",
             "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-1.4.7.tgz",
@@ -11465,6 +13238,11 @@
                 "xtend": "^4.0.0"
             }
         },
+        "node_modules/earcut": {
+            "version": "2.2.4",
+            "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
+            "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ=="
+        },
         "node_modules/eastasianwidth": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -13674,6 +15452,17 @@
             "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
             "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
         },
+        "node_modules/extend-shallow": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+            "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+            "dependencies": {
+                "is-extendable": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/external-editor": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -14601,7 +16390,6 @@
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
             "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
-            "dev": true,
             "funding": {
                 "url": "https://github.com/sponsors/ljharb"
             }
@@ -14701,6 +16489,49 @@
                 "node": ">=6.9.0"
             }
         },
+        "node_modules/geojson-equality": {
+            "version": "0.1.6",
+            "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz",
+            "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==",
+            "dependencies": {
+                "deep-equal": "^1.0.0"
+            }
+        },
+        "node_modules/geojson-rbush": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz",
+            "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==",
+            "dependencies": {
+                "@turf/bbox": "*",
+                "@turf/helpers": "6.x",
+                "@turf/meta": "6.x",
+                "@types/geojson": "7946.0.8",
+                "rbush": "^3.0.1"
+            }
+        },
+        "node_modules/geojson-rbush/node_modules/@types/geojson": {
+            "version": "7946.0.8",
+            "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz",
+            "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA=="
+        },
+        "node_modules/geojson-rbush/node_modules/quickselect": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+            "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+        },
+        "node_modules/geojson-rbush/node_modules/rbush": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
+            "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
+            "dependencies": {
+                "quickselect": "^2.0.0"
+            }
+        },
+        "node_modules/geojson-vt": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
+            "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg=="
+        },
         "node_modules/get-caller-file": {
             "version": "2.0.5",
             "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -14780,6 +16611,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/get-value": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+            "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/getpass": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -14797,6 +16636,11 @@
                 "omggif": "^1.0.10"
             }
         },
+        "node_modules/gl-matrix": {
+            "version": "3.4.3",
+            "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+            "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
+        },
         "node_modules/glob": {
             "version": "8.1.0",
             "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
@@ -15034,6 +16878,11 @@
             "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
             "dev": true
         },
+        "node_modules/grid-index": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz",
+            "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA=="
+        },
         "node_modules/gtoken": {
             "version": "7.0.1",
             "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz",
@@ -16501,7 +18350,6 @@
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
             "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
-            "dev": true,
             "dependencies": {
                 "has-tostringtag": "^1.0.0"
             },
@@ -16556,6 +18404,14 @@
                 "node": ">=0.4.0"
             }
         },
+        "node_modules/is-extendable": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+            "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -17235,6 +19091,11 @@
             "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
             "dev": true
         },
+        "node_modules/json-stringify-pretty-compact": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+            "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+        },
         "node_modules/json-stringify-safe": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -17916,6 +19777,41 @@
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
+        "node_modules/mapbox-gl": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.0.1.tgz",
+            "integrity": "sha512-o7C6sAlj6Hkdd4xQVEgQflgJYNYyZOAtYahhIOb9m8chI8umtWcCp8Ie0iGLYJvce1WHRMa3WGzs69ggwuWlDA==",
+            "dependencies": {
+                "@mapbox/geojson-rewind": "^0.5.2",
+                "@mapbox/jsonlint-lines-primitives": "^2.0.2",
+                "@mapbox/mapbox-gl-supported": "^2.0.1",
+                "@mapbox/point-geometry": "^0.1.0",
+                "@mapbox/tiny-sdf": "^2.0.6",
+                "@mapbox/unitbezier": "^0.0.1",
+                "@mapbox/vector-tile": "^1.3.1",
+                "@mapbox/whoots-js": "^3.1.0",
+                "cheap-ruler": "^3.0.1",
+                "csscolorparser": "~1.0.3",
+                "earcut": "^2.2.4",
+                "geojson-vt": "^3.2.1",
+                "gl-matrix": "^3.4.3",
+                "grid-index": "^1.1.0",
+                "kdbush": "^4.0.1",
+                "murmurhash-js": "^1.0.0",
+                "pbf": "^3.2.1",
+                "potpack": "^2.0.0",
+                "quickselect": "^2.0.0",
+                "rw": "^1.3.3",
+                "supercluster": "^8.0.0",
+                "tinyqueue": "^2.0.3",
+                "vt-pbf": "^3.1.3"
+            }
+        },
+        "node_modules/mapbox-gl/node_modules/quickselect": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+            "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+        },
         "node_modules/markdown-table": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -19479,6 +21375,11 @@
                 "node": "*"
             }
         },
+        "node_modules/murmurhash-js": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz",
+            "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw=="
+        },
         "node_modules/mute-stream": {
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -22598,11 +24499,25 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/object-is": {
+            "version": "1.1.5",
+            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
+            "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.3"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
         "node_modules/object-keys": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
             "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
-            "dev": true,
             "engines": {
                 "node": ">= 0.4"
             }
@@ -23300,6 +25215,18 @@
             "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
             "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
         },
+        "node_modules/pbf": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+            "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+            "dependencies": {
+                "ieee754": "^1.1.12",
+                "resolve-protobuf-schema": "^2.1.0"
+            },
+            "bin": {
+                "pbf": "bin/pbf"
+            }
+        },
         "node_modules/pdf-parse": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
@@ -23531,6 +25458,19 @@
                 "node": ">=12.13.0"
             }
         },
+        "node_modules/point-in-polygon": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
+            "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="
+        },
+        "node_modules/polygon-clipping": {
+            "version": "0.15.3",
+            "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz",
+            "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==",
+            "dependencies": {
+                "splaytree": "^3.1.0"
+            }
+        },
         "node_modules/postcss": {
             "version": "8.4.32",
             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
@@ -23647,6 +25587,11 @@
                 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
             }
         },
+        "node_modules/potpack": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
+            "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="
+        },
         "node_modules/prelude-ls": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -23856,6 +25801,11 @@
                 "prosemirror-transform": "^1.1.0"
             }
         },
+        "node_modules/protocol-buffers-schema": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
+            "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
+        },
         "node_modules/proxy-addr": {
             "version": "2.0.7",
             "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -24100,6 +26050,11 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/quickselect": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
+            "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="
+        },
         "node_modules/random-bytes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@@ -24174,6 +26129,14 @@
                 "url": "https://opencollective.com/webpack"
             }
         },
+        "node_modules/rbush": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz",
+            "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==",
+            "dependencies": {
+                "quickselect": "^1.0.1"
+            }
+        },
         "node_modules/rc-switch": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
@@ -24410,6 +26373,29 @@
                 "react": ">=0.14.0"
             }
         },
+        "node_modules/react-map-gl": {
+            "version": "7.1.6",
+            "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.6.tgz",
+            "integrity": "sha512-9XbrvpFF67Fyi+e6vRLJFnGpo3UF6ZHifIa8cS/wUYSsnv9sVyzGsN++FJy57zkz3Jh3kmf0xKZemR8K0FZLVw==",
+            "dependencies": {
+                "@maplibre/maplibre-gl-style-spec": "^19.2.1",
+                "@types/mapbox-gl": ">=1.0.0"
+            },
+            "peerDependencies": {
+                "mapbox-gl": ">=1.13.0",
+                "maplibre-gl": ">=1.13.0",
+                "react": ">=16.3.0",
+                "react-dom": ">=16.3.0"
+            },
+            "peerDependenciesMeta": {
+                "mapbox-gl": {
+                    "optional": true
+                },
+                "maplibre-gl": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/react-markdown": {
             "version": "9.0.1",
             "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
@@ -24775,7 +26761,6 @@
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
             "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==",
-            "dev": true,
             "dependencies": {
                 "call-bind": "^1.0.2",
                 "define-properties": "^1.2.0",
@@ -25197,6 +27182,14 @@
                 "node": ">=8"
             }
         },
+        "node_modules/resolve-protobuf-schema": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+            "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+            "dependencies": {
+                "protocol-buffers-schema": "^3.3.1"
+            }
+        },
         "node_modules/responselike": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -25913,7 +27906,6 @@
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
             "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
-            "dev": true,
             "dependencies": {
                 "define-data-property": "^1.0.1",
                 "functions-have-names": "^1.2.3",
@@ -25923,6 +27915,20 @@
                 "node": ">= 0.4"
             }
         },
+        "node_modules/set-value": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+            "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+            "dependencies": {
+                "extend-shallow": "^2.0.1",
+                "is-extendable": "^0.1.1",
+                "is-plain-object": "^2.0.3",
+                "split-string": "^3.0.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/setimmediate": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -26165,6 +28171,11 @@
             "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
             "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
         },
+        "node_modules/skmeans": {
+            "version": "0.9.7",
+            "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
+            "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg=="
+        },
         "node_modules/slice-ansi": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -26279,6 +28290,38 @@
                 "uuid": "dist/bin/uuid"
             }
         },
+        "node_modules/sort-asc": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz",
+            "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/sort-desc": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz",
+            "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/sort-object": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz",
+            "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==",
+            "dependencies": {
+                "bytewise": "^1.1.0",
+                "get-value": "^2.0.2",
+                "is-extendable": "^0.1.1",
+                "sort-asc": "^0.2.0",
+                "sort-desc": "^0.2.0",
+                "union-value": "^1.0.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/source-map": {
             "version": "0.5.7",
             "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -26359,6 +28402,11 @@
                 "wbuf": "^1.7.3"
             }
         },
+        "node_modules/splaytree": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz",
+            "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A=="
+        },
         "node_modules/split-on-first": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
@@ -26372,6 +28420,40 @@
             "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz",
             "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg=="
         },
+        "node_modules/split-string": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+            "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+            "dependencies": {
+                "extend-shallow": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/split-string/node_modules/extend-shallow": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
+            "dependencies": {
+                "assign-symbols": "^1.0.0",
+                "is-extendable": "^1.0.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/split-string/node_modules/is-extendable": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+            "dependencies": {
+                "is-plain-object": "^2.0.4"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/sprintf-js": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -27034,7 +29116,8 @@
         "node_modules/text-table": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true
         },
         "node_modules/textarea-caret": {
             "version": "3.1.0",
@@ -27073,6 +29156,11 @@
             "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
             "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
         },
+        "node_modules/tinyqueue": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz",
+            "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA=="
+        },
         "node_modules/titleize": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -27145,6 +29233,40 @@
                 "url": "https://github.com/sponsors/Borewit"
             }
         },
+        "node_modules/topojson-client": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
+            "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
+            "dependencies": {
+                "commander": "2"
+            },
+            "bin": {
+                "topo2geo": "bin/topo2geo",
+                "topomerge": "bin/topomerge",
+                "topoquantize": "bin/topoquantize"
+            }
+        },
+        "node_modules/topojson-client/node_modules/commander": {
+            "version": "2.20.3",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+        },
+        "node_modules/topojson-server": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz",
+            "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==",
+            "dependencies": {
+                "commander": "2"
+            },
+            "bin": {
+                "geo2topo": "bin/geo2topo"
+            }
+        },
+        "node_modules/topojson-server/node_modules/commander": {
+            "version": "2.20.3",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+        },
         "node_modules/touch": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
@@ -27820,6 +29942,11 @@
                 "node": "*"
             }
         },
+        "node_modules/turf-jsts": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz",
+            "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA=="
+        },
         "node_modules/tweetnacl": {
             "version": "0.14.5",
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
@@ -27963,6 +30090,19 @@
                 "node": ">=18"
             }
         },
+        "node_modules/typewise": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz",
+            "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==",
+            "dependencies": {
+                "typewise-core": "^1.2.0"
+            }
+        },
+        "node_modules/typewise-core": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz",
+            "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg=="
+        },
         "node_modules/typical": {
             "version": "2.6.1",
             "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
@@ -28082,6 +30222,20 @@
             "resolved": "https://registry.npmjs.org/uninstall/-/uninstall-0.0.0.tgz",
             "integrity": "sha512-pjP/0+A4gsbDVa8XH/S2GZdT9NPJW8NFMy3GI7HnsWG+NAmFSSj3QidNosXBI9cPtxxNExEDdhKFO6sli8K3mA=="
         },
+        "node_modules/union-value": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+            "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+            "dependencies": {
+                "arr-union": "^3.1.0",
+                "get-value": "^2.0.6",
+                "is-extendable": "^0.1.1",
+                "set-value": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/unist-util-is": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
@@ -28509,6 +30663,16 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/vt-pbf": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
+            "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==",
+            "dependencies": {
+                "@mapbox/point-geometry": "0.1.0",
+                "@mapbox/vector-tile": "^1.3.1",
+                "pbf": "^3.2.1"
+            }
+        },
         "node_modules/w3c-keyname": {
             "version": "2.2.8",
             "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
diff --git a/package.json b/package.json
index fa0dc3ff5..98453a651 100644
--- a/package.json
+++ b/package.json
@@ -109,6 +109,7 @@
         "@mui/material": "^5.14.19",
         "@octokit/core": "^5.0.2",
         "@react-google-maps/api": "^2.19.2",
+        "@turf/turf": "^6.5.0",
         "@types/bezier-js": "^4.1.3",
         "@types/cors": "^2.8.17",
         "@types/d3-axis": "^3.0.6",
@@ -197,6 +198,7 @@
         "jsonschema": "^1.4.1",
         "jszip": "^3.10.1",
         "lodash": "^4.17.21",
+        "mapbox-gl": "^3.0.1",
         "mathquill": "^0.10.1-a",
         "md5-file": "^5.0.0",
         "memorystream": "^0.3.1",
@@ -247,6 +249,7 @@
         "react-icons": "^4.12.0",
         "react-jsx-parser": "^1.29.0",
         "react-loading": "^2.0.3",
+        "react-map-gl": "^7.1.6",
         "react-markdown": "^9.0.1",
         "react-measure": "^2.5.2",
         "react-resizable": "^3.0.5",
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index a626772e4..e31c04c40 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -98,7 +98,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
 
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
     }
 
@@ -110,19 +110,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // toggles for user to decide which chart type to view the data in
     @computed get renderVizView() {
-        const scale = this.props.NativeDimScaling?.() || 1;
+        const scale = this._props.NativeDimScaling?.() || 1;
         const sharedProps = {
             Document: this.Document,
             layoutDoc: this.layoutDoc,
             records: this.records,
             axes: this.axes,
-            height: (this.props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
-            width: (this.props.PanelWidth() / scale) * 0.9,
+            height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
+            width: (this._props.PanelWidth() / scale) * 0.9,
             margin: { top: 10, right: 25, bottom: 75, left: 45 },
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />;
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
             case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
             case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
             case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} 
@@ -131,7 +131,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     render() {
-        const scale = this.props.NativeDimScaling?.() || 1;
+        const scale = this._props.NativeDimScaling?.() || 1;
 
         return !this.records.length ? (
             // displays how to get data into the DataVizBox if its empty
@@ -140,7 +140,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             <div
                 className="dataViz"
                 style={{
-                    pointerEvents: this.props.isContentActive() === true ? 'all' : 'none',
+                    pointerEvents: this._props.isContentActive() === true ? 'all' : 'none',
                     width: `${100 / scale}%`,
                     height: `${100 / scale}%`,
                     transform: `scale(${scale})`,
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index a1bd316f0..d786e84ad 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -14,6 +14,7 @@ import { undoable } from '../../../../util/UndoManager';
 import { PinProps, PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface HistogramProps {
     Document: Doc;
@@ -33,7 +34,7 @@ export interface HistogramProps {
 }
 
 @observer
-export class Histogram extends React.Component<HistogramProps> {
+export class Histogram extends ObservableReactComponent<HistogramProps> {
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _histogramRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _histogramSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@@ -45,46 +46,51 @@ export class Histogram extends React.Component<HistogramProps> {
     private selectedData: any = undefined; // Selection of selected bar
     private hoverOverData: any = undefined; // Selection of bar being hovered over
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @computed get _tableDataIds() {
-        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+        return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
     }
     // filters all data to just display selected data if brushed (created from an incoming link)
     @computed get _histogramData() {
-        if (this.props.axes.length < 1) return [];
-        if (this.props.axes.length < 2) {
-            var ax0 = this.props.axes[0];
-            if (/\d/.test(this.props.records[0][ax0])) {
+        if (this._props.axes.length < 1) return [];
+        if (this._props.axes.length < 2) {
+            var ax0 = this._props.axes[0];
+            if (/\d/.test(this._props.records[0][ax0])) {
                 this.numericalXData = true;
             }
-            return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
+            return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
         }
-        var ax0 = this.props.axes[0];
-        var ax1 = this.props.axes[1];
-        if (/\d/.test(this.props.records[0][ax0])) {
+        var ax0 = this._props.axes[0];
+        var ax1 = this._props.axes[1];
+        if (/\d/.test(this._props.records[0][ax0])) {
             this.numericalXData = true;
         }
-        if (/\d/.test(this.props.records[0][ax1])) {
+        if (/\d/.test(this._props.records[0][ax1])) {
             this.numericalYData = true;
         }
-        return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
+        return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this.props.axes[0];
-        var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
-        if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.records[0][ax1]) || !this.numericalYData) {
+        var ax0 = this._props.axes[0];
+        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        if (this._props.axes.length < 2 || !ax1 || !/\d/.test(this._props.records[0][ax1]) || !this.numericalYData) {
             return ax0 + ' Histogram';
         } else return ax0 + ' by ' + ax1 + ' Histogram';
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this._props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -114,16 +120,16 @@ export class Histogram extends React.Component<HistogramProps> {
         const anchor = Docs.Create.ConfigDocument({
             title: 'histogram doc selection' + this._currSelected,
         });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.Document);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
         return anchor;
     };
 
     @computed get height() {
-        return this.props.height - this.props.margin.top - this.props.margin.bottom;
+        return this._props.height - this._props.margin.top - this._props.margin.bottom;
     }
 
     @computed get width() {
-        return this.props.width - this.props.margin.left - this.props.margin.right;
+        return this._props.width - this._props.margin.left - this._props.margin.right;
     }
 
     // cleans data by converting numerical data to numbers and taking out empty cells
@@ -211,10 +217,10 @@ export class Histogram extends React.Component<HistogramProps> {
             .select(this._histogramRef.current)
             .append('svg')
             .attr('class', 'graph')
-            .attr('width', width + this.props.margin.right + this.props.margin.left)
-            .attr('height', height + this.props.margin.top + this.props.margin.bottom)
+            .attr('width', width + this._props.margin.right + this._props.margin.left)
+            .attr('height', height + this._props.margin.top + this._props.margin.bottom)
             .append('g')
-            .attr('transform', 'translate(' + this.props.margin.left + ',' + this.props.margin.top + ')'));
+            .attr('transform', 'translate(' + this._props.margin.left + ',' + this._props.margin.top + ')'));
         var x = d3
             .scaleLinear()
             .domain(this.numericalXData ? [startingPoint!, endingPoint!] : [0, numBins])
@@ -382,7 +388,7 @@ export class Histogram extends React.Component<HistogramProps> {
             )
             .attr('fill', d => {
                 var barColor;
-                const barColors = StrListCast(this.props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
+                const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
                     if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
                     else {
@@ -390,24 +396,24 @@ export class Histogram extends React.Component<HistogramProps> {
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
-                return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.dataViz_histogram_defaultColor);
+                return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
             });
     };
 
     @action changeSelectedColor = (color: string) => {
         this.curBarSelected.attr('fill', color);
-        const barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
 
-        const barColors = Cast(this.props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
+        const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
         barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
         barColors.push(StrCast(barName + '::' + color));
     };
 
     @action eraseSelectedColor = () => {
-        this.curBarSelected.attr('fill', this.props.layoutDoc.dataViz_histogram_defaultColor);
-        const barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+        this.curBarSelected.attr('fill', this._props.layoutDoc.dataViz_histogram_defaultColor);
+        const barName = StrCast(this._currSelected[this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
 
-        const barColors = Cast(this.props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
+        const barColors = Cast(this._props.layoutDoc.dataViz_histogram_barColors, listSpec('string'), null);
         barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1));
     };
 
@@ -416,7 +422,7 @@ export class Histogram extends React.Component<HistogramProps> {
         if (svg)
             svg.selectAll('rect').attr('fill', (d: any) => {
                 var barColor;
-                const barColors = StrListCast(this.props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
+                const barColors = StrListCast(this._props.layoutDoc.dataViz_histogram_barColors).map(each => each.split('::'));
                 barColors.forEach(each => {
                     if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1];
                     else {
@@ -424,7 +430,7 @@ export class Histogram extends React.Component<HistogramProps> {
                         if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1];
                     }
                 });
-                return barColor ? StrCast(barColor) : StrCast(this.props.layoutDoc.dataViz_histogram_defaultColor);
+                return barColor ? StrCast(barColor) : StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor);
             });
     };
 
@@ -433,14 +439,14 @@ export class Histogram extends React.Component<HistogramProps> {
         this._histogramData;
         var curSelectedBarName = '';
         var titleAccessor: any = '';
-        if (this.props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this.props.axes[0] + '-' + this.props.axes[1];
-        else if (this.props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + this.props.axes[0];
-        if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
-        if (!this.props.layoutDoc.dataViz_histogram_defaultColor) this.props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
-        if (!this.props.layoutDoc.dataViz_histogram_barColors) this.props.layoutDoc.dataViz_histogram_barColors = new List<string>();
+        if (this._props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0] + '-' + this._props.axes[1];
+        else if (this._props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0];
+        if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+        if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
+        if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
         var selected = 'none';
         if (this._currSelected) {
-            curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
+            curSelectedBarName = StrCast(this._currSelected![this._props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\</g, ''));
             selected = '{ ';
             Object.keys(this._currSelected).forEach(key =>
                 key //
@@ -450,17 +456,17 @@ export class Histogram extends React.Component<HistogramProps> {
             selected = selected.substring(0, selected.length - 2) + ' }';
         }
         var selectedBarColor;
-        var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::'));
+        var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
         barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1]));
 
         if (this._histogramData.length > 0 || !this.parentViz) {
-            return this.props.axes.length >= 1 ? (
+            return this._props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
                         <EditableText
-                            val={StrCast(this.props.layoutDoc[titleAccessor])}
+                            val={StrCast(this._props.layoutDoc[titleAccessor])}
                             setVal={undoable(
-                                action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+                                action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
                                 'Change Graph Title'
                             )}
                             color={'black'}
@@ -472,9 +478,9 @@ export class Histogram extends React.Component<HistogramProps> {
                             tooltip={'Change Default Bar Color'}
                             type={Type.SEC}
                             icon={<FaFillDrip />}
-                            selectedColor={StrCast(this.props.layoutDoc.dataViz_histogram_defaultColor)}
-                            setFinalColor={undoable(color => (this.props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
-                            setSelectedColor={undoable(color => (this.props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+                            selectedColor={StrCast(this._props.layoutDoc.dataViz_histogram_defaultColor)}
+                            setFinalColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
+                            setSelectedColor={undoable(color => (this._props.layoutDoc.dataViz_histogram_defaultColor = color), 'Change Default Bar Color')}
                             size={Size.XSMALL}
                         />
                     </div>
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 7e0391879..abb95aa4c 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,6 +1,6 @@
 import { EditableText, Size } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
@@ -14,6 +14,7 @@ import { PinProps, PresBox } from '../../trails';
 import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface DataPoint {
     x: number;
@@ -40,33 +41,38 @@ export interface LineChartProps {
 }
 
 @observer
-export class LineChart extends React.Component<LineChartProps> {
+export class LineChart extends ObservableReactComponent<LineChartProps> {
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
+    constructor(props:any) {
+        super(props);
+        makeObservable(this);
+    }
+
 
     @computed get _tableDataIds() {
-        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+        return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
     }
     @computed get _lineChartData() {
-        var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds);
-        if (this.props.axes.length <= 1) return [];
-        return this._tableData.map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+        var guids = StrListCast(this._props.layoutDoc.dataViz_rowIds);
+        if (this._props.axes.length <= 1) return [];
+        return this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
     }
     @computed get graphTitle() {
-        return this.props.axes[1] + ' vs. ' + this.props.axes[0] + ' Line Chart';
+        return this._props.axes[1] + ' vs. ' + this._props.axes[0] + ' Line Chart';
     }
     @computed get parentViz() {
-        return DocCast(this.props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
+        return DocCast(this._props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
         //     .filter(link => {
-        //         return link.link_anchor_1 == this.props.Document.dataViz_parentViz;
+        //         return link.link_anchor_1 == this._props.Document.dataViz_parentViz;
         //     }) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
@@ -94,7 +100,7 @@ export class LineChart extends React.Component<LineChartProps> {
             { fireImmediately: true }
         );
         this._disposers.annos = reaction(
-            () => DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']),
+            () => DocListCast(this._props.dataDoc[this._props.fieldKey + '_annotations']),
             annotations => {
                 // modify how d3 renders so that anything in this annotations list would be potentially highlighted in some way
                 // could be blue colored to make it look like anchor
@@ -114,7 +120,7 @@ export class LineChart extends React.Component<LineChartProps> {
                 // redraw annotations when the chart data has changed, or the local or inherited selection has changed
                 this.clearAnnotations();
                 selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true);
-                incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]])));
+                incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this._props.axes[0]]), Number(record[this._props.axes[1]])));
             },
             { fireImmediately: true }
         );
@@ -170,23 +176,23 @@ export class LineChart extends React.Component<LineChartProps> {
             //
             title: 'line doc selection' + this._currSelected?.x,
         });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.Document);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
         anchor.config_dataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined;
         return anchor;
     };
 
     @computed get height() {
-        return this.props.height - this.props.margin.top - this.props.margin.bottom;
+        return this._props.height - this._props.margin.top - this._props.margin.bottom;
     }
 
     @computed get width() {
-        return this.props.width - this.props.margin.left - this.props.margin.right;
+        return this._props.width - this._props.margin.left - this._props.margin.right;
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this.props.axes[0];
-        var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
-        if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
+        var ax0 = this._props.axes[0];
+        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
             return ax0 + ' Line Chart';
         } else return ax1 + ' by ' + ax0 + ' Line Chart';
     }
@@ -210,7 +216,7 @@ export class LineChart extends React.Component<LineChartProps> {
         // TODO: nda - get rid of svg element in the list?
         if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined;
         else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined;
-        this.props.records.forEach(record => record[this.props.axes[0]] === x && record[this.props.axes[1]] === y && (record.selected = true));
+        this._props.records.forEach(record => record[this._props.axes[0]] === x && record[this._props.axes[1]] === y && (record.selected = true));
     }
 
     drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear<number, number, never>, yScale: d3.ScaleLinear<number, number, never>) {
@@ -245,7 +251,7 @@ export class LineChart extends React.Component<LineChartProps> {
         const yScale = scaleCreatorNumerical(0, yMax, height, 0);
 
         // adding svg
-        const margin = this.props.margin;
+        const margin = this._props.margin;
         const svg = (this._lineChartSvg = d3
             .select(this._lineChartRef.current)
             .append('svg')
@@ -317,7 +323,7 @@ export class LineChart extends React.Component<LineChartProps> {
         svg.append('text')
             .attr('transform', 'translate(' + width / 2 + ' ,' + (height + 40) + ')')
             .style('text-anchor', 'middle')
-            .text(this.props.axes[0]);
+            .text(this._props.axes[0]);
         svg.append('text')
             .attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
             .attr('x', -(height / 2))
@@ -325,7 +331,7 @@ export class LineChart extends React.Component<LineChartProps> {
             .attr('height', 20)
             .attr('width', 20)
             .style('text-anchor', 'middle')
-            .text(this.props.axes[1]);
+            .text(this._props.axes[1]);
     };
 
     private updateTooltip(
@@ -346,18 +352,18 @@ export class LineChart extends React.Component<LineChartProps> {
 
     render() {
         var titleAccessor: any = '';
-        if (this.props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this.props.axes[0] + '-' + this.props.axes[1];
-        else if (this.props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this.props.axes[0];
-        if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
-        const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none';
+        if (this._props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0] + '-' + this._props.axes[1];
+        else if (this._props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0];
+        if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+        const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
         if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
-            return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? (
+            return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
                 <div className="chart-container">
                     <div className="graph-title">
                         <EditableText
-                            val={StrCast(this.props.layoutDoc[titleAccessor])}
+                            val={StrCast(this._props.layoutDoc[titleAccessor])}
                             setVal={undoable(
-                                action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+                                action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
                                 'Change Graph Title'
                             )}
                             color={'black'}
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index c2e03744e..9e7265b26 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,7 +1,7 @@
 import { Checkbox } from '@mui/material';
 import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -13,6 +13,7 @@ import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
 import { PinProps, PresBox } from '../../trails';
 import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface PieChartProps {
     Document: Doc;
@@ -32,7 +33,7 @@ export interface PieChartProps {
 }
 
 @observer
-export class PieChart extends React.Component<PieChartProps> {
+export class PieChart extends ObservableReactComponent<PieChartProps> {
     private _disposers: { [key: string]: IReactionDisposer } = {};
     private _piechartRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _piechartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
@@ -41,44 +42,49 @@ export class PieChart extends React.Component<PieChartProps> {
     private hoverOverData: any = undefined; // Selection of slice being hovered over
     @observable _currSelected: any | undefined = undefined; // Object of selected slice
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @computed get _tableDataIds() {
-        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+        return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
     }
 
     // organized by specified number percentages/ratios if one column is selected and it contains numbers
     // otherwise, assume data is organized by categories
     @computed get byCategory() {
-        return !/\d/.test(this.props.records[0][this.props.axes[0]]) || this.props.layoutDoc.dataViz_pie_asHistogram;
+        return !/\d/.test(this._props.records[0][this._props.axes[0]]) || this._props.layoutDoc.dataViz_pie_asHistogram;
     }
     // filters all data to just display selected data if brushed (created from an incoming link)
     @computed get _pieChartData() {
-        if (this.props.axes.length < 1) return [];
+        if (this._props.axes.length < 1) return [];
 
-        const ax0 = this.props.axes[0];
-        if (this.props.axes.length < 2) {
-            return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] }));
+        const ax0 = this._props.axes[0];
+        if (this._props.axes.length < 2) {
+            return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
         }
-        const ax1 = this.props.axes[1];
-        return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] }));
+        const ax1 = this._props.axes[1];
+        return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
     }
 
     @computed get defaultGraphTitle() {
-        var ax0 = this.props.axes[0];
-        var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined;
-        if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) {
+        var ax0 = this._props.axes[0];
+        var ax1 = this._props.axes.length > 1 ? this._props.axes[1] : undefined;
+        if (this._props.axes.length < 2 || !/\d/.test(this._props.records[0][ax0]) || !ax1) {
             return ax0 + ' Pie Chart';
         }
         return ax1 + ' by ' + ax0 + ' Pie Chart';
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this._props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -101,16 +107,16 @@ export class PieChart extends React.Component<PieChartProps> {
             //
             title: 'piechart doc selection' + this._currSelected,
         });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.Document);
+        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this._props.Document);
         return anchor;
     };
 
     @computed get height() {
-        return this.props.height - this.props.margin.top - this.props.margin.bottom;
+        return this._props.height - this._props.margin.top - this._props.margin.bottom;
     }
 
     @computed get width() {
-        return this.props.width - this.props.margin.left - this.props.margin.right;
+        return this._props.width - this._props.margin.left - this._props.margin.right;
     }
 
     // cleans data by converting numerical data to numbers and taking out empty cells
@@ -181,7 +187,7 @@ export class PieChart extends React.Component<PieChartProps> {
 
         var percentField = Object.keys(dataSet[0])[0];
         var descriptionField = Object.keys(dataSet[0])[1]!;
-        var radius = Math.min(width, height - this.props.margin.top - this.props.margin.bottom) / 2;
+        var radius = Math.min(width, height - this._props.margin.top - this._props.margin.bottom) / 2;
 
         // converts data into Objects
         var data = this.data(dataSet);
@@ -209,10 +215,10 @@ export class PieChart extends React.Component<PieChartProps> {
             .select(this._piechartRef.current)
             .append('svg')
             .attr('class', 'graph')
-            .attr('width', width + this.props.margin.right + this.props.margin.left)
-            .attr('height', height + this.props.margin.top + this.props.margin.bottom)
+            .attr('width', width + this._props.margin.right + this._props.margin.left)
+            .attr('height', height + this._props.margin.top + this._props.margin.bottom)
             .append('g'));
-        let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this.props.margin.left) + ',' + height / 2 + ')');
+        let g = svg.append('g').attr('transform', 'translate(' + (width / 2 + this._props.margin.left) + ',' + height / 2 + ')');
         var pie = d3.pie();
         var arc = d3.arc().innerRadius(0).outerRadius(radius);
 
@@ -249,7 +255,7 @@ export class PieChart extends React.Component<PieChartProps> {
             } catch (error) {}
             possibleDataPointVals.push(dataPointVal);
         });
-        const sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+        const sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         arcs.append('path')
             .attr('fill', (d, i) => {
                 var dataPoint;
@@ -261,7 +267,7 @@ export class PieChart extends React.Component<PieChartProps> {
                 }
                 var sliceColor;
                 if (dataPoint) {
-                    const sliceTitle = dataPoint[this.props.axes[0]];
+                    const sliceTitle = dataPoint[this._props.axes[0]];
                     const accessByName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
                     sliceColors.forEach(each => each[0] == accessByName && (sliceColor = each[1]));
                 }
@@ -309,10 +315,10 @@ export class PieChart extends React.Component<PieChartProps> {
 
     @action changeSelectedColor = (color: string) => {
         this.curSliceSelected.attr('fill', color);
-        const sliceTitle = this._currSelected[this.props.axes[0]];
+        const sliceTitle = this._currSelected[this._props.axes[0]];
         const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
 
-        const sliceColors = Cast(this.props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
+        const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null);
         sliceColors.map(each => {
             if (each.split('::')[0] == sliceName) sliceColors.splice(sliceColors.indexOf(each), 1);
         });
@@ -320,20 +326,20 @@ export class PieChart extends React.Component<PieChartProps> {
     };
 
     @action changeHistogramCheckBox = () => {
-        this.props.layoutDoc.dataViz_pie_asHistogram = !this.props.layoutDoc.dataViz_pie_asHistogram;
+        this._props.layoutDoc.dataViz_pie_asHistogram = !this._props.layoutDoc.dataViz_pie_asHistogram;
         this.drawChart(this._pieChartData, this.width, this.height);
     };
 
     render() {
         var titleAccessor: any = '';
-        if (this.props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this.props.axes[0] + '-' + this.props.axes[1];
-        else if (this.props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this.props.axes[0];
-        if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
-        if (!this.props.layoutDoc.dataViz_pie_sliceColors) this.props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
+        if (this._props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this._props.axes[0] + '-' + this._props.axes[1];
+        else if (this._props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this._props.axes[0];
+        if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
+        if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
         var selected: string;
         var curSelectedSliceName = '';
         if (this._currSelected) {
-            const sliceTitle = this._currSelected[this.props.axes[0]];
+            const sliceTitle = this._currSelected[this._props.axes[0]];
             curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
             selected = '{ ';
             Object.keys(this._currSelected).map(key => {
@@ -343,19 +349,19 @@ export class PieChart extends React.Component<PieChartProps> {
             selected += ' }';
         } else selected = 'none';
         var selectedSliceColor;
-        var sliceColors = StrListCast(this.props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
+        var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
         sliceColors.forEach(each => {
             if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1];
         });
 
         if (this._pieChartData.length > 0 || !this.parentViz) {
-            return this.props.axes.length >= 1 ? (
+            return this._props.axes.length >= 1 ? (
                 <div className="chart-container">
                     <div className="graph-title">
                         <EditableText
-                            val={StrCast(this.props.layoutDoc[titleAccessor])}
+                            val={StrCast(this._props.layoutDoc[titleAccessor])}
                             setVal={undoable(
-                                action(val => (this.props.layoutDoc[titleAccessor] = val as string)),
+                                action(val => (this._props.layoutDoc[titleAccessor] = val as string)),
                                 'Change Graph Title'
                             )}
                             color={'black'}
@@ -363,9 +369,9 @@ export class PieChart extends React.Component<PieChartProps> {
                             fillWidth
                         />
                     </div>
-                    {this.props.axes.length === 1 && /\d/.test(this.props.records[0][this.props.axes[0]]) ? (
-                        <div className={'asHistogram-checkBox'} style={{ width: this.props.width }}>
-                            <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this.props.layoutDoc.dataViz_pie_asHistogram as boolean} />
+                    {this._props.axes.length === 1 && /\d/.test(this._props.records[0][this._props.axes[0]]) ? (
+                        <div className={'asHistogram-checkBox'} style={{ width: this._props.width }}>
+                            <Checkbox color="primary" onChange={this.changeHistogramCheckBox} checked={this._props.layoutDoc.dataViz_pie_asHistogram as boolean} />
                             Organize data as histogram
                         </div>
                     ) : null}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 3e7d3af8c..f77c138df 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,5 +1,5 @@
 import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
@@ -11,6 +11,7 @@ import { DragManager } from '../../../../util/DragManager';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 const {  default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
 interface TableBoxProps {
     Document: Doc;
@@ -30,13 +31,17 @@ interface TableBoxProps {
 }
 
 @observer
-export class TableBox extends React.Component<TableBoxProps> {
+export class TableBox extends ObservableReactComponent<TableBoxProps> {
     _inputChangedDisposer?: IReactionDisposer;
     _containerRef: HTMLDivElement | null = null;
 
     @observable _scrollTop = -1;
     @observable _tableHeight = 0;
     @observable _tableContainerHeight = 0;
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
 
     componentDidMount() {
         // if the tableData changes (ie., when records are selected by the parent (input) visulization),
@@ -48,17 +53,17 @@ export class TableBox extends React.Component<TableBoxProps> {
         this._inputChangedDisposer?.();
     }
     @computed get _tableDataIds() {
-        return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
+        return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
     // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
     @computed get _tableData() {
-        return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]);
+        return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
     }
 
     @computed get parentViz() {
-        return DocCast(this.props.Document.dataViz_parentViz);
-        // return LinkManager.Instance.getAllRelatedLinks(this.props.Document) // out of all links
-        //     .filter(link => link.link_anchor_1 == this.props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
+        return DocCast(this._props.Document.dataViz_parentViz);
+        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
+        //     .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
         //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
     }
 
@@ -68,14 +73,14 @@ export class TableBox extends React.Component<TableBoxProps> {
 
     // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
     filterSelectedRowsDown = () => {
-        const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows);
-        this.props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
-        const highlighted = NumListCast(this.props.layoutDoc.dataViz_highlitedRows);
-        this.props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
+        const selected = NumListCast(this._props.layoutDoc.dataViz_selectedRows);
+        this._props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
+        const highlighted = NumListCast(this._props.layoutDoc.dataViz_highlitedRows);
+        this._props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
     };
 
     @computed get viewScale() {
-        return this.props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
+        return this._props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
     }
     @computed get rowHeight() {
         console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
@@ -89,14 +94,14 @@ export class TableBox extends React.Component<TableBoxProps> {
         return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
     }
     @action handleScroll = () => {
-        if (!this.props.docView?.()?.ContentDiv?.hidden) {
+        if (!this._props.docView?.()?.ContentDiv?.hidden) {
             this._scrollTop = this._containerRef?.scrollTop ?? 0;
         }
     };
     @action
     tableRowClick = (e: React.MouseEvent, rowId: number) => {
-        const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
-        const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
+        const highlited = Cast(this._props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
+        const selected = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
         if (e.metaKey) {
             // highlighting a row
             if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
@@ -120,26 +125,26 @@ export class TableBox extends React.Component<TableBoxProps> {
             e,
             e => {
                 // dragging off a column to create a brushed DataVizBox
-                const sourceAnchorCreator = () => this.props.docView?.()!.Document!;
+                const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
                 const targetCreator = (annotationOn: Doc | undefined) => {
-                    const embedding = Doc.MakeEmbedding(this.props.docView?.()!.Document!);
+                    const embedding = Doc.MakeEmbedding(this._props.docView?.()!.Document!);
                     embedding._dataViz = DataVizView.TABLE;
                     embedding._dataViz_axes = new List<string>([col, col]);
-                    embedding._dataViz_parentViz = this.props.Document;
+                    embedding._dataViz_parentViz = this._props.Document;
                     embedding.annotationOn = annotationOn;
-                    embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors);
-                    embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor;
-                    embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors);
+                    embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
+                    embedding.defaultHistogramColor = this._props.layoutDoc.defaultHistogramColor;
+                    embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
                     return embedding;
                 };
-                if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
+                if (this._props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
+                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                         dragComplete: e => {
                             if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
                                 e.linkDocument.link_displayLine = true;
                                 e.linkDocument.link_matchEmbeddings = true;
                                 e.linkDocument.link_displayArrow = true;
-                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
                                 // e.annoDragData.linkSourceDoc.followLinkZoom = false;
                             }
                         },
@@ -150,11 +155,11 @@ export class TableBox extends React.Component<TableBoxProps> {
             },
             emptyFunction,
             action(e => {
-                const newAxes = this.props.axes;
+                const newAxes = this._props.axes;
                 if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
                 else if (newAxes.length > 1) newAxes[1] = col;
                 else newAxes.push(col);
-                this.props.selectAxes(newAxes);
+                this._props.selectAxes(newAxes);
             })
         );
     };
@@ -166,14 +171,14 @@ export class TableBox extends React.Component<TableBoxProps> {
                     className="tableBox"
                     tabIndex={0}
                     onKeyDown={e => {
-                        if (this.props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
+                        if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
                             e.stopPropagation();
-                            this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
+                            this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
                         }
                     }}>
                     <div className="selectAll-buttons">
-                        <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
-                        <Button onClick={action(() => (this.props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
+                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
+                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
                     </div>
                     <div
                         className={`tableBox-container ${this.columns[0]}`}
@@ -181,7 +186,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                         onScroll={this.handleScroll}
                         ref={action((r: HTMLDivElement | null) => {
                             this._containerRef = r;
-                            if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+                            if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
                                 this._tableContainerHeight = r.getBoundingClientRect().height ?? 0;
                                 r.addEventListener(
                                     'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
@@ -196,7 +201,7 @@ export class TableBox extends React.Component<TableBoxProps> {
                         <table
                             className="tableBox-table"
                             ref={action((r: HTMLTableElement | null) => {
-                                if (!this.props.docView?.()?.ContentDiv?.hidden && r) {
+                                if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
                                     this._tableHeight = r?.getBoundingClientRect().height ?? 0;
                                 }
                             })}>
@@ -207,8 +212,8 @@ export class TableBox extends React.Component<TableBoxProps> {
                                         <th
                                             key={this.columns.indexOf(col)}
                                             style={{
-                                                color: this.props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this.props.axes.lastElement() === col ? 'darkred' : undefined,
-                                                background: this.props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this.props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+                                                color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this._props.axes.lastElement() === col ? 'darkred' : undefined,
+                                                background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this._props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
                                                 fontWeight: 'bolder',
                                                 border: '3px solid black',
                                             }}
@@ -227,13 +232,13 @@ export class TableBox extends React.Component<TableBoxProps> {
                                             className={`tableBox-row ${this.columns[0]}`}
                                             onClick={e => this.tableRowClick(e, rowId)}
                                             style={{
-                                                background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+                                                background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
                                             }}>
                                             {this.columns.map(col => {
-                                                const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false;
+                                                const colSelected = this._props.axes.length > 1 ? this._props.axes[0] == col || this._props.axes[1] == col : this._props.axes.length > 0 ? this._props.axes[0] == col : false;
                                                 return (
                                                     <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
-                                                        <div className="tableBox-cell">{this.props.records[rowId][col]}</div>
+                                                        <div className="tableBox-cell">{this._props.records[rowId][col]}</div>
                                                     </td>
                                                 );
                                             })}
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 256acbf13..13ce5b7e2 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -1,13 +1,13 @@
-import mapboxgl from "mapbox-gl";
-import { MercatorCoordinate } from "mapbox-gl";
-import { MapRef } from "react-map-gl";
+import mapboxgl from 'mapbox-gl';
+import { MercatorCoordinate } from 'mapbox-gl';
+import { MapRef } from 'react-map-gl';
 import * as React from 'react';
-import * as d3 from "d3";
+import * as d3 from 'd3';
 import * as turf from '@turf/turf';
-import { Position } from "@turf/turf";
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "geojson";
-import { observer } from "mobx-react";
-import { action, computed, observable } from "mobx";
+import { Position } from '@turf/turf';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+import { observer } from 'mobx-react';
+import { action, computed, observable } from 'mobx';
 
 export enum AnimationStatus {
     START = 'start',
@@ -21,37 +21,35 @@ export enum AnimationSpeed {
     FAST = '3x',
 }
 
-@observer
 export class AnimationUtility {
-    private SMOOTH_FACTOR = 0.95
-    private ROUTE_COORDINATES: Position[] = []; 
+    private SMOOTH_FACTOR = 0.95;
+    private ROUTE_COORDINATES: Position[] = [];
     private PATH: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>;
     private FLY_IN_START_PITCH = 40;
-    private FIRST_LNG_LAT: {lng: number, lat: number};
+    private FIRST_LNG_LAT: { lng: number; lat: number };
     private START_ALTITUDE = 3_000_000;
 
     @observable private isStreetViewAnimation: boolean;
     @observable private animationSpeed: AnimationSpeed;
 
-    
-    private previousLngLat: {lng: number, lat: number};
+    private previousLngLat: { lng: number; lat: number };
 
     private currentStreetViewBearing: number = 0;
 
     @computed get flyInEndBearing() {
-        return this.isStreetViewAnimation ? 
-            this.calculateBearing(
-                {
-                    lng: this.ROUTE_COORDINATES[0][0],
-                    lat: this.ROUTE_COORDINATES[0][1]
-                },
-                {
-                    lng: this.ROUTE_COORDINATES[1][0],
-                    lat: this.ROUTE_COORDINATES[1][1]
-                }
-            )
+        return this.isStreetViewAnimation
+            ? this.calculateBearing(
+                  {
+                      lng: this.ROUTE_COORDINATES[0][0],
+                      lat: this.ROUTE_COORDINATES[0][1],
+                  },
+                  {
+                      lng: this.ROUTE_COORDINATES[1][0],
+                      lat: this.ROUTE_COORDINATES[1][1],
+                  }
+              )
             : -20;
-        }
+    }
 
     @computed get flyInStartBearing() {
         return Math.max(0, Math.min(this.flyInEndBearing + 20, 360)); // between 0 and 360
@@ -80,8 +78,8 @@ export class AnimationUtility {
 
     @computed get animationDuration(): number {
         return 20_000;
-        // compute path distance for a standard speed 
-        // get animation speed 
+        // compute path distance for a standard speed
+        // get animation speed
         // get isStreetViewAnimation (should be slower if so)
     }
 
@@ -90,19 +88,13 @@ export class AnimationUtility {
         // calculate new flyToDuration and animationDuration
         this.animationSpeed = speed;
     }
-    
+
     @action
     public updateIsStreetViewAnimation(isStreetViewAnimation: boolean) {
         this.isStreetViewAnimation = isStreetViewAnimation;
     }
 
-
-    constructor(
-        firstLngLat: {lng: number, lat: number},
-        routeCoordinates: Position[],
-        isStreetViewAnimation: boolean,
-        animationSpeed: AnimationSpeed
-    ) {
+    constructor(firstLngLat: { lng: number; lat: number }, routeCoordinates: Position[], isStreetViewAnimation: boolean, animationSpeed: AnimationSpeed) {
         this.FIRST_LNG_LAT = firstLngLat;
         this.previousLngLat = firstLngLat;
         this.ROUTE_COORDINATES = routeCoordinates;
@@ -111,11 +103,11 @@ export class AnimationUtility {
         const bearing = this.calculateBearing(
             {
                 lng: routeCoordinates[0][0],
-                lat: routeCoordinates[0][1]
+                lat: routeCoordinates[0][1],
             },
             {
                 lng: routeCoordinates[1][0],
-                lat: routeCoordinates[1][1]
+                lat: routeCoordinates[1][1],
             }
         );
         this.currentStreetViewBearing = bearing;
@@ -137,293 +129,245 @@ export class AnimationUtility {
         currentAnimationPhase,
         updateAnimationPhase,
         updateFrameId,
-      }: {
-        map: MapRef,
+    }: {
+        map: MapRef;
         // path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>,
         // startBearing: number,
         // startAltitude: number,
         // pitch: number,
-        currentAnimationPhase: number,
-        updateAnimationPhase: ( 
-            newAnimationPhase: number,
-            ) => void,
+        currentAnimationPhase: number;
+        updateAnimationPhase: (newAnimationPhase: number) => void;
         updateFrameId: (newFrameId: number) => void;
+    }) => {
+        return new Promise<void>(async resolve => {
+            const pathDistance = turf.lineDistance(this.PATH);
+            console.log('PATH DISTANCE: ', pathDistance);
+            let startTime: number | null = null;
+
+            const frame = async (currentTime: number) => {
+                if (!startTime) startTime = currentTime;
+                const elapsedSinceLastFrame = currentTime - startTime;
+                const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
+                const animationPhase = currentAnimationPhase + phaseIncrement;
+
+                // when the duration is complete, resolve the promise and stop iterating
+                if (animationPhase > 1) {
+                    resolve();
+                    return;
+                }
 
-      }) => {
-        return new Promise<void>(async (resolve) => {
-          const pathDistance = turf.lineDistance(this.PATH);
-          console.log("PATH DISTANCE: ", pathDistance);
-          let startTime: number | null = null;
-      
-          const frame = async (currentTime: number) => {
-            if (!startTime) startTime = currentTime;
-            const elapsedSinceLastFrame = currentTime - startTime;
-            const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
-            const animationPhase = currentAnimationPhase + phaseIncrement;
-      
-            // when the duration is complete, resolve the promise and stop iterating
-            if (animationPhase > 1) {
-              resolve();
-              return;
-            }
+                // calculate the distance along the path based on the animationPhase
+                const alongPath = turf.along(this.PATH, pathDistance * animationPhase).geometry.coordinates;
+
+                const lngLat = {
+                    lng: alongPath[0],
+                    lat: alongPath[1],
+                };
+
+                let bearing: number;
+                if (this.isStreetViewAnimation) {
+                    bearing = this.lerp(
+                        this.currentStreetViewBearing,
+                        this.calculateBearing(this.previousLngLat, lngLat),
+                        0.028 // Adjust the transition speed as needed
+                    );
+                    this.currentStreetViewBearing = bearing;
+                    // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
+                } else {
+                    // slowly rotate the map at a constant rate
+                    bearing = this.flyInEndBearing - animationPhase * 200.0;
+                    // bearing = startBearing - animationPhase * 200.0;
+                }
+
+                this.previousLngLat = lngLat;
+
+                updateAnimationPhase(animationPhase);
+
+                // compute corrected camera ground position, so that he leading edge of the path is in view
+                var correctedPosition = this.computeCameraPosition(
+                    this.isStreetViewAnimation,
+                    this.flyInEndPitch,
+                    bearing,
+                    lngLat,
+                    this.flyInEndAltitude,
+                    true // smooth
+                );
+
+                // set the pitch and bearing of the camera
+                const camera = map.getFreeCameraOptions();
+                camera.setPitchBearing(this.flyInEndPitch, bearing);
+
+                console.log('Corrected pos: ', correctedPosition);
+                console.log('Start altitude: ', this.flyInEndAltitude);
+                // set the position and altitude of the camera
+                camera.position = MercatorCoordinate.fromLngLat(correctedPosition, this.flyInEndAltitude);
+
+                // apply the new camera options
+                map.setFreeCameraOptions(camera);
 
-      
-            // calculate the distance along the path based on the animationPhase
-            const alongPath = turf.along(this.PATH, pathDistance * animationPhase).geometry
-              .coordinates;
-      
-            const lngLat = {
-              lng: alongPath[0],
-              lat: alongPath[1],
+                // repeat!
+                const innerFrameId = await window.requestAnimationFrame(frame);
+                updateFrameId(innerFrameId);
             };
-      
-            let bearing: number;
-            if (this.isStreetViewAnimation){
-                bearing = this.lerp(
-                    this.currentStreetViewBearing,
-                    this.calculateBearing(this.previousLngLat, lngLat),
-                    0.028 // Adjust the transition speed as needed
-                  );
-                  this.currentStreetViewBearing = bearing;
-                // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
-            } else {
-                // slowly rotate the map at a constant rate
-                bearing = this.flyInEndBearing - animationPhase * 200.0;
-                // bearing = startBearing - animationPhase * 200.0;
-            }
 
-            this.previousLngLat = lngLat;
-            
-            updateAnimationPhase(animationPhase);
-      
-            // compute corrected camera ground position, so that he leading edge of the path is in view
-            var correctedPosition = this.computeCameraPosition(
-              this.isStreetViewAnimation,
-              this.flyInEndPitch,
-              bearing,
-              lngLat,
-              this.flyInEndAltitude,
-              true // smooth
-            );
-      
-            // set the pitch and bearing of the camera
-            const camera = map.getFreeCameraOptions();
-            camera.setPitchBearing(this.flyInEndPitch, bearing);
-      
-            console.log("Corrected pos: ", correctedPosition);
-            console.log("Start altitude: ", this.flyInEndAltitude);
-            // set the position and altitude of the camera
-            camera.position = MercatorCoordinate.fromLngLat(
-              correctedPosition,
-              this.flyInEndAltitude
-            );
-
-      
-            // apply the new camera options
-            map.setFreeCameraOptions(camera);
-      
-            // repeat!
-            const innerFrameId = await window.requestAnimationFrame(frame);
-            updateFrameId(innerFrameId);
-          };
-      
-          const outerFrameId = await window.requestAnimationFrame(frame);
-          updateFrameId(outerFrameId);
+            const outerFrameId = await window.requestAnimationFrame(frame);
+            updateFrameId(outerFrameId);
         });
-      };
+    };
 
-    public flyInAndRotate = async ({
-        map,
-        updateFrameId
-      }:
-      {
-        map: MapRef,
-        updateFrameId: (newFrameId: number) => void
-      }
-      ) => {
-        return new Promise<{bearing: number, altitude: number}>(async (resolve) => {
-          let start: number | null;
-      
-          var currentAltitude;
-          var currentBearing;
-          var currentPitch;
-      
-          // the animation frame will run as many times as necessary until the duration has been reached
-          const frame = async (time: number) => {
-            if (!start) {
-              start = time;
-            }
-      
-            // otherwise, use the current time to determine how far along in the duration we are
-            let animationPhase = (time - start) / this.flyToDuration;
-      
-            // because the phase calculation is imprecise, the final zoom can vary
-            // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
-            if (animationPhase > 1) {
-              animationPhase = 1;
-            }
-      
-            currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase)
-            // rotate the camera between startBearing and endBearing
-            currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase)
-      
-            currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase)
-      
-            // compute corrected camera ground position, so the start of the path is always in view
-            var correctedPosition = this.computeCameraPosition(
-              false,
-              currentPitch,
-              currentBearing,
-              this.FIRST_LNG_LAT,
-              currentAltitude
-            );
-      
-            // set the pitch and bearing of the camera
-            const camera = map.getFreeCameraOptions();
-            camera.setPitchBearing(currentPitch, currentBearing);
-      
-            // set the position and altitude of the camera
-            camera.position = MercatorCoordinate.fromLngLat(
-              correctedPosition,
-              currentAltitude
-            );
-      
-            // apply the new camera options
-            map.setFreeCameraOptions(camera);
-      
-            // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
-            if (animationPhase === 1) {
-              resolve({
-                bearing: currentBearing,
-                altitude: currentAltitude,
-              });
-      
-              // return so there are no further iterations of this frame
-              return;
-            }
-      
-            const innerFrameId = await window.requestAnimationFrame(frame);
-            updateFrameId(innerFrameId);
-            
-          };
-      
-         const outerFrameId = await window.requestAnimationFrame(frame);
-         updateFrameId(outerFrameId);
+    public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
+        return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+            let start: number | null;
+
+            var currentAltitude;
+            var currentBearing;
+            var currentPitch;
+
+            // the animation frame will run as many times as necessary until the duration has been reached
+            const frame = async (time: number) => {
+                if (!start) {
+                    start = time;
+                }
+
+                // otherwise, use the current time to determine how far along in the duration we are
+                let animationPhase = (time - start) / this.flyToDuration;
+
+                // because the phase calculation is imprecise, the final zoom can vary
+                // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
+                if (animationPhase > 1) {
+                    animationPhase = 1;
+                }
+
+                currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase);
+                // rotate the camera between startBearing and endBearing
+                currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase);
+
+                currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
+
+                // compute corrected camera ground position, so the start of the path is always in view
+                var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+
+                // set the pitch and bearing of the camera
+                const camera = map.getFreeCameraOptions();
+                camera.setPitchBearing(currentPitch, currentBearing);
+
+                // set the position and altitude of the camera
+                camera.position = MercatorCoordinate.fromLngLat(correctedPosition, currentAltitude);
+
+                // apply the new camera options
+                map.setFreeCameraOptions(camera);
+
+                // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
+                if (animationPhase === 1) {
+                    resolve({
+                        bearing: currentBearing,
+                        altitude: currentAltitude,
+                    });
+
+                    // return so there are no further iterations of this frame
+                    return;
+                }
+
+                const innerFrameId = await window.requestAnimationFrame(frame);
+                updateFrameId(innerFrameId);
+            };
+
+            const outerFrameId = await window.requestAnimationFrame(frame);
+            updateFrameId(outerFrameId);
         });
-      };
+    };
 
-      previousCameraPosition: {lng: number, lat: number} | null = null;
+    previousCameraPosition: { lng: number; lat: number } | null = null;
 
-      lerp = (start: number, end: number, amt: number) => {
+    lerp = (start: number, end: number, amt: number) => {
         return (1 - amt) * start + amt * end;
-      }
-
-      computeCameraPosition = (
-        isStreetViewAnimation: boolean,
-        pitch: number,
-        bearing: number,
-        targetPosition: {lng: number, lat: number},
-        altitude: number,
-        smooth = false
-      ) => {
+    };
+
+    computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
         const bearingInRadian = (bearing * Math.PI) / 180;
-        const pitchInRadian = ((90 - pitch)* Math.PI) / 180;
+        const pitchInRadian = ((90 - pitch) * Math.PI) / 180;
 
         let correctedLng = targetPosition.lng;
         let correctedLat = targetPosition.lat;
 
         if (!isStreetViewAnimation) {
-            const lngDiff =
-                ((altitude / Math.tan(pitchInRadian)) *
-                Math.sin(-bearingInRadian)) /
-                70000; // ~70km/degree longitude
-            const latDiff =
-                ((altitude / Math.tan(pitchInRadian)) *
-                Math.cos(-bearingInRadian)) /
-                110000 // 110km/degree latitude
+            const lngDiff = ((altitude / Math.tan(pitchInRadian)) * Math.sin(-bearingInRadian)) / 70000; // ~70km/degree longitude
+            const latDiff = ((altitude / Math.tan(pitchInRadian)) * Math.cos(-bearingInRadian)) / 110000; // 110km/degree latitude
 
             correctedLng = targetPosition.lng + lngDiff;
             correctedLat = targetPosition.lat - latDiff;
-
         }
-        
+
         const newCameraPosition = {
-          lng: correctedLng,
-          lat: correctedLat
+            lng: correctedLng,
+            lat: correctedLat,
         };
-      
+
         if (smooth) {
-          if (this.previousCameraPosition) {
-            newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
-            newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
-          }
+            if (this.previousCameraPosition) {
+                newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
+                newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
+            }
         }
-      
-        this.previousCameraPosition = newCameraPosition
-      
+
+        this.previousCameraPosition = newCameraPosition;
+
         return newCameraPosition;
-      };
-      
-      public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties>=> {
+    };
+
+    public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties> => {
         const coords = {
-          latitude: center[1],
-          longitude: center[0],
+            latitude: center[1],
+            longitude: center[0],
         };
         const km = radiusInKm;
         const ret = [];
-        const distanceX = km / (111.320 * Math.cos((coords.latitude * Math.PI) / 180));
+        const distanceX = km / (111.32 * Math.cos((coords.latitude * Math.PI) / 180));
         const distanceY = km / 110.574;
         let theta;
         let x;
         let y;
         for (let i = 0; i < points; i += 1) {
-          theta = (i / points) * (2 * Math.PI);
-          x = distanceX * Math.cos(theta);
-          y = distanceY * Math.sin(theta);
-          ret.push([coords.longitude + x, coords.latitude + y]);
+            theta = (i / points) * (2 * Math.PI);
+            x = distanceX * Math.cos(theta);
+            y = distanceY * Math.sin(theta);
+            ret.push([coords.longitude + x, coords.latitude + y]);
         }
         ret.push(ret[0]);
         return {
-          type: 'Feature',
-          geometry: {
-            type: 'Polygon',
-            coordinates: [ret],
-          },
-          properties: {}
+            type: 'Feature',
+            geometry: {
+                type: 'Polygon',
+                coordinates: [ret],
+            },
+            properties: {},
         };
-      }
+    };
 
-      private calculateBearing(
-        from: { lng: number; lat: number },
-        to: { lng: number; lat: number }
-      ): number {
+    private calculateBearing(from: { lng: number; lat: number }, to: { lng: number; lat: number }): number {
         const lon1 = from.lng;
         const lat1 = from.lat;
         const lon2 = to.lng;
         const lat2 = to.lat;
-      
+
         const lon1Rad = (lon1 * Math.PI) / 180;
         const lon2Rad = (lon2 * Math.PI) / 180;
         const lat1Rad = (lat1 * Math.PI) / 180;
         const lat2Rad = (lat2 * Math.PI) / 180;
-      
+
         const y = Math.sin(lon2Rad - lon1Rad) * Math.cos(lat2Rad);
-        const x =
-          Math.cos(lat1Rad) * Math.sin(lat2Rad) -
-          Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
-      
-        let bearing = Math.atan2(y,x);
-      
+        const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
+
+        let bearing = Math.atan2(y, x);
+
         // Convert bearing from radians to degrees
         bearing = (bearing * 180) / Math.PI;
-      
+
         // Ensure the bearing is within [0, 360)
         if (bearing < 0) {
-          bearing += 360;
+            bearing += 360;
         }
-      
-        return bearing;
-      }
-
 
-}
\ No newline at end of file
+        return bearing;
+    }
+}
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index bf4028f01..f9607becf 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -1,15 +1,15 @@
-import React = require('react');
-import { observer } from "mobx-react";
-import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
-import { IReactionDisposer, ObservableMap, reaction } from "mobx";
-import { Doc, Opt } from "../../../../fields/Doc";
-import { returnFalse, unimplementedFunction } from "../../../../Utils";
-import { NumCast, StrCast } from "../../../../fields/Types";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { IconButton } from "browndash-components";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { SettingsManager } from "../../../util/SettingsManager";
-import { IconLookup, faAdd, faCalendarDays, faRoute } from "@fortawesome/free-solid-svg-icons";
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { SelectionManager } from '../../../util/SelectionManager';
+import { IconButton } from 'browndash-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { SettingsManager } from '../../../util/SettingsManager';
+import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -32,9 +32,9 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     private title: string | undefined = undefined;
 
-    public setPinDoc(pinDoc: Doc){
-        this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`) ;
-        console.log("Title: ", this.title)
+    public setPinDoc(pinDoc: Doc) {
+        this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
+        console.log('Title: ', this.title);
     }
 
     public get Active() {
@@ -54,7 +54,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentDidMount() {
         this._disposer = reaction(
-            () => SelectionManager.Views().slice(),
+            () => SelectionManager.Views.slice(),
             sel => DirectionsAnchorMenu.Instance.fadeOut(true)
         );
     }
@@ -95,43 +95,28 @@ export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     render() {
         const buttons = (
-            <div className='directions-menu-buttons' style={{display: 'flex'}}>
+            <div className="directions-menu-buttons" style={{ display: 'flex' }}>
                 <IconButton
                     tooltip="Add route" //
                     onPointerDown={this.Delete}
                     icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
                     color={SettingsManager.userColor}
                 />
-            
-            
-                <IconButton
-                    tooltip='Animate route'
-                    onPointerDown={this.Delete} /**TODO: fix */
-                    icon={<FontAwesomeIcon icon={faRoute as IconLookup}/>}
-                    color={SettingsManager.userColor}
-                />
-                <IconButton
-                    tooltip='Add to calendar'
-                    onPointerDown={this.Delete} /**TODO: fix */
-                    icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup}/>}
-                    color={SettingsManager.userColor}
-                />
+
+                <IconButton tooltip="Animate route" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />
+                <IconButton tooltip="Add to calendar" onPointerDown={this.Delete} /**TODO: fix */ icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
             </div>
-        )
+        );
 
         return this.getElement(
-            <div ref={DirectionsAnchorMenu.top} style={{ height: 'max-content' , width: '100%', display: 'flex', flexDirection: 'column' }}>
+            <div ref={DirectionsAnchorMenu.top} style={{ height: 'max-content', width: '100%', display: 'flex', flexDirection: 'column' }}>
                 <div>{this.title}</div>
-                <div className='direction-inputs' style={{display: 'flex', flexDirection: 'column'}}>
-                    <input
-                        placeholder="Origin"
-                    />
-                    <input
-                        placeholder="Destination"
-                    />
+                <div className="direction-inputs" style={{ display: 'flex', flexDirection: 'column' }}>
+                    <input placeholder="Origin" />
+                    <input placeholder="Destination" />
                 </div>
                 {buttons}
             </div>
-        )
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index a6182991d..6a14427c0 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -43,7 +43,7 @@ import { MapProvider, Map as MapboxMap } from 'react-map-gl';
  * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
  */
 
-const mapboxApiKey = "pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg";
+const mapboxApiKey = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNsbnc2eHJpbTA1ZTUyam85aGx4Z2FhbGwifQ.2Kqw9mk-9wAAg9kmHmKzcg';
 const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/  and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
 
 /**
@@ -198,7 +198,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 title="Toggle Sidebar"
                 style={{
                     display: !this.props.isContentActive() ? 'none' : undefined,
-                    top: StrCast(this.rootDoc._layout_showTitle) === 'title' ? 20 : 5,
+                    top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
                 onPointerDown={this.sidebarBtnDown}>
@@ -230,8 +230,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         });
 
         const targetCreator = (annotationOn: Doc | undefined) => {
-            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-            FormattedTextBox.SelectOnLoad = target[Id];
+            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+            FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
         const docView = this.props.DocumentView?.();
@@ -324,7 +324,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     };
 
     @observable
-    bingSearchBarContents: any = this.rootDoc.map; // For Bing Maps: The contents of the Bing search bar (string)
+    bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string)
 
     geoDataRequestOptions = {
         entityType: 'PopulatedPlace',
@@ -357,9 +357,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     deselectPin = () => {
         if (this.selectedPin) {
             // Removes filter
-            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
 
             const temp = this.selectedPin;
             if (!this._unmounting) {
@@ -372,7 +372,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             }
             this.map_docToPinMap.set(temp, newpin);
             this.selectedPin = undefined;
-            this.bingSearchBarContents = this.rootDoc.map;
+            this.bingSearchBarContents = this.Document.map;
         }
     };
 
@@ -389,9 +389,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         this.selectedPin = pinDoc;
         this.bingSearchBarContents = pinDoc.map;
 
-        // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match');
-        // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match');
-        Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
 
         this.recolorPin(this.selectedPin, 'green');
 
@@ -460,8 +460,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
         /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
         const anchor = Docs.Create.ConfigDocument({
-            title: 'MapAnchor:' + this.rootDoc.title,
-            text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location',
+            title: 'MapAnchor:' + this.Document.title,
+            text: StrCast(this.selectedPin?.map) || StrCast(this.Document.map) || 'map location',
             config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
             config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
             config_map_zoom: NumCast(this.dataDoc.map_zoom),
@@ -469,15 +469,15 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
             layout_unrendered: true,
             mapPin: existingPin ?? this.selectedPin,
-            annotationOn: this.rootDoc,
+            annotationOn: this.Document,
         });
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
             addAsAnnotation && this.addDocument(anchor);
-            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
             return anchor;
         }
-        return this.rootDoc;
+        return this.Document;
     };
 
     map_docToPinMap = new Map<Doc, any>();
@@ -524,9 +524,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     deleteSelectedPin = undoable(() => {
         if (this.selectedPin) {
             // Removes filter
-            Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove');
-            Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+            Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+            Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
 
             this.removePushpin(this.selectedPin);
         }
@@ -614,7 +614,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change'));
 
         this._disposers.mapLocation = reaction(
-            () => this.rootDoc.map,
+            () => this.Document.map,
             mapLoc => (this.bingSearchBarContents = mapLoc),
             { fireImmediately: true }
         );
@@ -639,7 +639,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         );
 
         this._disposers.location = reaction(
-            () => ({ lat: this.rootDoc.latitude, lng: this.rootDoc.longitude, zoom: this.rootDoc.map_zoom, mapType: this.rootDoc.map_type }),
+            () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }),
             locationObject => {
                 // if (this._bingMap.current)
                 try {
@@ -690,7 +690,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 }
             },
             e => {
-                const createPin = () => this.createPushpin(this.rootDoc.latitude, this.rootDoc.longitude, this.rootDoc.map);
+                const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map);
                 if (this.bingSearchBarContents) {
                     this.bingSearch().then(createPin);
                 } else createPin();
@@ -706,7 +706,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     render() {
         // bcz:  no idea what's going on here, but bings maps have some kind of bug
         // such that we need to delay rendering a second map on startup until the first map is rendered.
-        this.rootDoc[DocCss];
+        this.Document[DocCss];
         if (MapBoxContainer._rerenderDelay) {
             // prettier-ignore
             this._rerenderTimeout = this._rerenderTimeout ??
@@ -715,7 +715,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                         MapBoxContainer._rerenderDelay = 0;
                     }
                     this._rerenderTimeout = undefined;
-                    this.rootDoc[DocCss] = this.rootDoc[DocCss] + 1;
+                    this.Document[DocCss] = this.Document[DocCss] + 1;
                 }), MapBoxContainer._rerenderDelay);
             return null;
         }
@@ -732,7 +732,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                     style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                     <div style={{ mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>
                     {renderAnnotations(this.opaqueFilter)}
-                    {SnappingManager.GetIsDragging() ? null : renderAnnotations()}
+                    {SnappingManager.IsDragging ? null : renderAnnotations()}
 
                     <div className="mapBox-searchbar">
                         <EditableText
@@ -758,15 +758,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                         </div>
                     </div>
                     <MapProvider>
-                        <MapboxMap
-                            id="mabox-map"
-                            mapStyle={`mapbox://styles/mapbox/streets-v9`}
-                            mapboxAccessToken={mapboxApiKey}
-                        />
+                        <MapboxMap id="mabox-map" mapStyle={`mapbox://styles/mapbox/streets-v9`} mapboxAccessToken={mapboxApiKey} />
                     </MapProvider>
-                    
-  
-{/* 
+
+                    {/* 
                     <BingMapsReact
                         onMapReady={this.bingMapReady} //
                         bingMapsKey={bingApiKey}
@@ -786,7 +781,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                                           {...this.props}
                                           renderDepth={this.props.renderDepth + 1}
                                           Document={pushpin}
-                                          DataDoc={undefined}
                                           PanelWidth={returnOne}
                                           PanelHeight={returnOne}
                                           NativeWidth={returnOne}
@@ -824,7 +818,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                         ref={this._sidebarRef}
                         {...this.props}
                         fieldKey={this.fieldKey}
-                        rootDoc={this.rootDoc}
+                        Document={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         usePanelWidth={true}
diff --git a/webpack.config.js b/webpack.config.js
index a153616b8..d92086bc2 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -12,7 +12,7 @@ const plugins = [
     // new ForkTsCheckerWebpackPlugin({
     //     typescript: {
     //         // useTypescriptIncrementalApi: true,
-    //         eslint: true,
+    //         // eslint: true,
     //         memoryLimit: 8192,
     //     },
     //     // tslint: true,
-- 
cgit v1.2.3-70-g09d2


From 7ff12fb05b7fdb067052c19e5a09cd4c21961556 Mon Sep 17 00:00:00 2001
From: srichman333 <sarah_n_richman@brown.edu>
Date: Wed, 27 Dec 2023 02:30:33 -0500
Subject: dragging tables column fix

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx          | 2 +-
 src/client/views/nodes/DataVizBox/components/TableBox.tsx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 56c130e0f..e8e46edbc 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -331,7 +331,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         if (!keys) return;
         const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
         var current: {[key: string]: string}[] = []
-        for (let i=1; i<children.length; i++){
+        for (let i=0; i<children.length; i++){
             var row: {[key:string]: string} = {};
             if (children[i]){
                 for (let j=0; j<keys.length; j++){
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 16e66d0c3..4f60a156b 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -226,7 +226,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter((rowId, i) => this.startID <= i && i <= this.endID)
+                                    .filter((rowId, i) => this.startID-2 <= i && i <= this.endID+2)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
-- 
cgit v1.2.3-70-g09d2


From 9b9f54a43793ca6ffb26c56f962d11ba8325abd2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Dec 2023 17:01:40 -0500
Subject: cleaned up imports, mobx observable initialization and some compile
 errors.

---
 src/Utils.ts                                       |  27 ++---
 src/client/util/CalendarManager.tsx                |  34 +++---
 src/client/util/CaptureManager.tsx                 |   2 +-
 src/client/util/CurrentUserUtils.ts                |  14 +--
 src/client/util/GroupManager.tsx                   |   2 +-
 src/client/util/HypothesisUtils.ts                 |  13 ++-
 src/client/views/DocumentButtonBar.tsx             |   5 -
 src/client/views/DocumentDecorations.tsx           |  11 +-
 src/client/views/FilterPanel.tsx                   |  11 +-
 src/client/views/GestureOverlay.tsx                |  25 +++--
 src/client/views/InkTranscription.tsx              |   8 +-
 src/client/views/InkingStroke.tsx                  |  76 +++++---------
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/MetadataEntryMenu.tsx             |  12 +--
 src/client/views/PreviewCursor.tsx                 |   2 +-
 src/client/views/PropertiesButtons.tsx             |  17 +--
 .../views/PropertiesDocBacklinksSelector.tsx       |   4 +-
 src/client/views/PropertiesSection.tsx             |   6 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 src/client/views/UndoStack.tsx                     |  22 ++--
 src/client/views/animationtimeline/Region.tsx      |   2 +-
 src/client/views/animationtimeline/Timeline.tsx    |   4 +-
 .../views/animationtimeline/TimelineOverview.tsx   |   2 +-
 src/client/views/animationtimeline/Track.tsx       |   8 +-
 .../views/collections/CollectionCalendarView.tsx   |  19 ++--
 .../views/collections/CollectionDockingView.tsx    |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |   3 -
 .../CollectionNoteTakingViewDivider.tsx            |   2 +-
 .../views/collections/CollectionPileView.tsx       |   2 +-
 .../collections/CollectionStackedTimeline.tsx      |   4 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 .../views/collections/CollectionTimeView.tsx       |  14 +--
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/KeyRestrictionRow.tsx |  37 +++----
 src/client/views/collections/TreeView.tsx          |   2 +-
 .../CollectionFreeFormBackgroundGrid.tsx           |   2 +-
 .../CollectionFreeFormInfoState.tsx                |   2 +-
 .../CollectionFreeFormInfoUI.tsx                   |   8 +-
 .../CollectionFreeFormLinksView.tsx                |   4 +-
 .../CollectionFreeFormPannableContents.tsx         |   5 +-
 .../CollectionFreeFormRemoteCursors.tsx            |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |   5 +-
 .../collectionGrid/CollectionGridView.tsx          |   4 +-
 .../views/collections/collectionGrid/Grid.tsx      |   2 +-
 .../CollectionMulticolumnView.tsx                  |   4 +-
 .../CollectionMultirowView.tsx                     |   3 +-
 .../collectionMulticolumn/MulticolumnResizer.tsx   |   8 +-
 .../MulticolumnWidthLabel.tsx                      |  27 +++--
 .../collectionMulticolumn/MultirowHeightLabel.tsx  |  22 ++--
 .../collectionMulticolumn/MultirowResizer.tsx      |   6 +-
 .../collectionSchema/SchemaColumnHeader.tsx        |   4 +-
 src/client/views/linking/LinkMenuItem.tsx          |   6 +-
 src/client/views/linking/LinkPopup.tsx             |   8 +-
 .../views/linking/LinkRelationshipSearch.tsx       |   2 +-
 .../views/newlightbox/ButtonMenu/ButtonMenu.tsx    |   4 +-
 src/client/views/newlightbox/NewLightboxView.tsx   |  26 ++---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   4 +-
 .../views/nodes/DataVizBox/SchemaCSVPopUp.tsx      |  30 +++---
 .../nodes/DataVizBox/components/Histogram.tsx      |   6 +-
 .../nodes/DataVizBox/components/LineChart.tsx      |  28 ++---
 .../views/nodes/DataVizBox/components/PieChart.tsx |   6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |  12 +--
 src/client/views/nodes/DocumentContentsView.tsx    |   6 +-
 src/client/views/nodes/DocumentView.tsx            |   7 +-
 src/client/views/nodes/FaceRectangle.tsx           |   4 +-
 src/client/views/nodes/FaceRectangles.tsx          |   4 +-
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/KeyValuePair.tsx            |   6 +-
 src/client/views/nodes/LinkDescriptionPopup.tsx    |   2 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   8 +-
 src/client/views/nodes/LoadingBox.tsx              |   2 +-
 src/client/views/nodes/MapBox/AnimationUtility.ts  |  13 +--
 .../views/nodes/MapBox/DirectionsAnchorMenu.tsx    |  14 +--
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    | 106 +++++++------------
 src/client/views/nodes/MapBox/MapBox.tsx           |  60 +++++------
 src/client/views/nodes/MapBox/MapBox2.tsx          |   2 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |  20 ++--
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 .../nodes/PhysicsBox/PhysicsSimulationBox.tsx      |   4 +-
 .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx   |   2 +-
 src/client/views/nodes/RadialMenu.tsx              |   2 +-
 src/client/views/nodes/RadialMenuItem.tsx          |   2 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |   4 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   2 +-
 src/client/views/nodes/ScriptingBox.tsx            |   8 +-
 src/client/views/nodes/TaskCompletedBox.tsx        |   8 +-
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/audio/AudioWaveform.tsx     |   2 +-
 src/client/views/nodes/calendarBox/CalendarBox.tsx | 114 +++++++++------------
 .../views/nodes/formattedText/DashDocView.tsx      |   6 +-
 .../views/nodes/formattedText/EquationView.tsx     |   7 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |   2 +-
 .../views/nodes/importBox/ImportElementBox.tsx     |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          |   6 +-
 src/client/views/pdf/Annotation.tsx                |   5 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  16 +--
 src/client/views/pdf/PDFViewer.tsx                 |   2 +-
 src/client/views/search/SearchBox.tsx              |  15 ++-
 src/client/views/selectedDoc/SelectedDocView.tsx   |   6 +-
 src/client/views/topbar/TopBar.tsx                 |   4 +-
 src/client/views/webcam/DashWebRTCVideo.tsx        |   5 +-
 src/debug/Repl.tsx                                 |  10 +-
 src/debug/Viewer.tsx                               |  18 ++--
 src/fields/CursorField.ts                          |   7 +-
 src/fields/Doc.ts                                  |   4 +-
 src/fields/List.ts                                 |   8 +-
 src/fields/Proxy.ts                                |  12 +--
 src/fields/util.ts                                 |   4 +-
 src/mobile/ImageUpload.tsx                         |   6 +-
 src/mobile/MobileInkOverlay.tsx                    |   2 +-
 src/mobile/MobileInterface.tsx                     | 110 ++++++++++----------
 src/typings/index.d.ts                             |   4 +-
 test/test.ts                                       | 106 ++++++++++---------
 116 files changed, 650 insertions(+), 793 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index a060e4a2c..b5c218e01 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,13 +1,12 @@
-import * as uuid from 'uuid';
 import { ColorResult } from 'react-color';
+import * as uuid from 'uuid';
 //import { Socket } from '../node_modules/socket.io-client';
-import { Socket } from '../node_modules/socket.io/dist/index';
+import * as Color from 'color';
 import * as rp from 'request-promise';
+import { Socket } from '../node_modules/socket.io/dist/index';
 import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
-import * as Color from 'color';
-import { action } from 'mobx';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -896,23 +895,19 @@ export function setupMoveUpEvents(
     document.addEventListener('click', _clickEvent, true);
 }
 
-export function dateRangeStrToDates (dateStr: string) {
+export function dateRangeStrToDates(dateStr: string) {
     // dateStr in yyyy-mm-dd format
-    const dateRangeParts = dateStr.split("|"); // splits into from and to date
-    const fromParts = dateRangeParts[0].split("-");
-    const toParts = dateRangeParts[1].split("-");
+    const dateRangeParts = dateStr.split('|'); // splits into from and to date
+    const fromParts = dateRangeParts[0].split('-');
+    const toParts = dateRangeParts[1].split('-');
 
     const fromYear = parseInt(fromParts[0]);
-    const fromMonth = parseInt(fromParts[1])-1;
+    const fromMonth = parseInt(fromParts[1]) - 1;
     const fromDay = parseInt(fromParts[2]);
 
     const toYear = parseInt(toParts[0]);
-    const toMonth = parseInt(toParts[1])-1;
+    const toMonth = parseInt(toParts[1]) - 1;
     const toDay = parseInt(toParts[2]);
 
-
-    return [
-        new Date(fromYear, fromMonth, fromDay),
-        new Date(toYear, toMonth, toDay)
-    ];
-}
\ No newline at end of file
+    return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
+}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6ef2d3429..6e9094b3a 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,26 +1,24 @@
-import * as React from 'react';
-import './CalendarManager.scss';
+import { TextField } from '@mui/material';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import * as React from 'react';
+import Select from 'react-select';
 import { Doc, DocListCast } from '../../fields/Doc';
-import { DocumentView } from '../views/nodes/DocumentView';
+import { DocData } from '../../fields/DocSymbols';
+import { StrCast } from '../../fields/Types';
 import { DictationOverlay } from '../views/DictationOverlay';
-import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
 import { MainViewModal } from '../views/MainViewModal';
-import { TextField } from '@mui/material';
-import Select from 'react-select';
-import { SettingsManager } from './SettingsManager';
-import { DateCast, DocCast, StrCast } from '../../fields/Types';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
+import './CalendarManager.scss';
 import { DocumentManager } from './DocumentManager';
-import { DocData } from '../../fields/DocSymbols';
+import { SelectionManager } from './SelectionManager';
+import { SettingsManager } from './SettingsManager';
 // import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { Button } from 'browndash-components';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Provider, defaultTheme } from '@adobe/react-spectrum';
-import { DateValue } from '@internationalized/date';
-import { DateRangePicker, SpectrumDateRangePickerProps } from '@adobe/react-spectrum';
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
 import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button } from 'browndash-components';
 import { Docs } from '../documents/Documents';
 import { ObservableReactComponent } from '../views/ObservableReactComponent';
 // import 'react-date-range/dist/styles.css';
@@ -51,8 +49,8 @@ const formatCalendarDateToString = (calendarDate: any) => {
 export class CalendarManager extends ObservableReactComponent<{}> {
     public static Instance: CalendarManager;
     @observable private isOpen = false;
-    @observable private targetDoc: Doc | undefined; // the target document
-    @observable private targetDocView: DocumentView | undefined; // the DocumentView of the target doc
+    @observable private targetDoc: Doc | undefined = undefined; // the target document
+    @observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
     @observable private dialogueBoxOpacity = 1; // for the modal
     @observable private overlayOpacity = 0.4; // for the modal
 
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 071fc1ee9..c1e0a5b2e 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -15,7 +15,7 @@ import { SelectionManager } from './SelectionManager';
 export class CaptureManager extends React.Component<{}> {
     public static Instance: CaptureManager;
     static _settingsStyle = addStyleSheet();
-    @observable _document: any;
+    @observable _document: any = undefined;
     @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
 
     constructor(props: {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c1d74c398..3f28f44fc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,5 +1,6 @@
 import { observable, reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
+import { OmitKeys, Utils } from "../../Utils";
 import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
 import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -8,29 +9,28 @@ import { RichTextField } from "../../fields/RichTextField";
 import { listSpec } from "../../fields/Schema";
 import { ScriptField } from "../../fields/ScriptField";
 import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
-import { nullAudio, WebField } from "../../fields/URLField";
+import { WebField, nullAudio } from "../../fields/URLField";
 import { SetCachedGroups, SharingPermissions } from "../../fields/util";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
-import { OmitKeys, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
-import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
 import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
 import { DashboardView } from "../views/DashboardView";
+import { OverlayView } from "../views/OverlayView";
+import { TreeViewType } from "../views/collections/CollectionTreeView";
 import { Colors } from "../views/global/globalEnums";
 import { media_state } from "../views/nodes/AudioBox";
-import { DocumentView, OpenWhere } from "../views/nodes/DocumentView";
+import { OpenWhere } from "../views/nodes/DocumentView";
 import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox";
 import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { OverlayView } from "../views/OverlayView";
 import { DragManager, dropActionType } from "./DragManager";
 import { MakeTemplate } from "./DropConverter";
 import { FollowLinkScript } from "./LinkFollower";
 import { LinkManager } from "./LinkManager";
 import { ScriptingGlobals } from "./ScriptingGlobals";
 import { ColorScheme } from "./SettingsManager";
-import { UndoManager } from "./UndoManager";
 import { SnappingManager } from "./SnappingManager";
+import { UndoManager } from "./UndoManager";
 
 interface Button {
     // DocumentOptions fields a button can set
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 90f65b648..f4f879208 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -33,7 +33,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
     @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
     @observable private users: string[] = []; // list of users populated from the database.
     @observable private selectedUsers: UserOptions[] | null = null; // list of users selected in the "Select users" dropdown.
-    @observable currentGroup: Opt<Doc>; // the currently selected group.
+    @observable currentGroup: Opt<Doc> = undefined; // the currently selected group.
     @observable private createGroupModalOpen: boolean = false;
     private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
     private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // the ref for the group creation button
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 990798ed3..0ae23d793 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,16 +1,15 @@
-import { StrCast, Cast } from '../../fields/Types';
-import { SearchUtil } from './SearchUtil';
 import { action, runInAction } from 'mobx';
+import { simulateMouseClick } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
+import { Cast, StrCast } from '../../fields/Types';
+import { WebField } from '../../fields/URLField';
 import { DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
-import { SelectionManager } from './SelectionManager';
-import { WebField } from '../../fields/URLField';
-import { DocumentManager } from './DocumentManager';
 import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
-import { simulateMouseClick, Utils } from '../../Utils';
 import { DocumentView } from '../views/nodes/DocumentView';
-import { Id } from '../../fields/FieldSymbols';
+import { DocumentManager } from './DocumentManager';
+import { SearchUtil } from './SearchUtil';
+import { SelectionManager } from './SelectionManager';
 
 export namespace Hypothesis {
     /**
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index f0887676f..3938b81cd 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -40,14 +40,9 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     private _pullAnimating = false;
     private _pushAnimating = false;
     private _pullColorAnimating = false;
-    @observable private pushIcon: IconProp = 'arrow-alt-circle-up';
-    @observable private pullIcon: IconProp = 'arrow-alt-circle-down';
-    @observable private pullColor: string = 'white';
     @observable public isAnimatingFetch = false;
     @observable public isAnimatingPulse = false;
 
-    @observable private openHover: UtilityButtonState = UtilityButtonState.Default;
-
     @observable public static Instance: DocumentButtonBar;
     public static hasPushedHack = false;
     public static hasPulledHack = false;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d2f23c2f3..dab6e6193 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -12,7 +12,7 @@ import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, lightOrDark, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
@@ -85,7 +85,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     }
 
     @computed get ClippedBounds() {
-        const bounds = this.Bounds;
+        const bounds = { ...this.Bounds };
         const leftBounds = this._props.boundsLeft;
         const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop;
         bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
@@ -106,9 +106,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             : { x: Math.min(rect.left, bounds.x),
                                 y: Math.min(rect.top, bounds.y),
                                 r: Math.max(rect.right, bounds.r),
-                                b: Math.max(rect.bottom, bounds.b),
-                                c: SelectionManager.Views.length === 1 ? rect.center : undefined },
-                    { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as { X: number; Y: number } | undefined }); // prettier-ignore
+                                b: Math.max(rect.bottom, bounds.b)},
+                    { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); // prettier-ignore
     }
 
     @action
@@ -811,7 +810,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="documentDecorations-borderRadius"
                                     style={{
-                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : SettingsManager.userColor}`,
+                                        background: `${this._isRounding ? Colors.MEDIUM_BLUE : lightOrDark(StrCast(seldocview.layoutDoc._backgroundColor, SettingsManager.userColor))}`,
                                         transform: `translate(${radiusHandleLocation ?? 0}px, ${(radiusHandleLocation ?? 0) + (this._showNothing ? 0 : this._titleHeight)}px)`,
                                     }}
                                     onPointerDown={this.onRadiusDown}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index a3c4350d9..d3075a5cd 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,24 +1,23 @@
-import * as React from 'react';
 import { action, computed, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
 import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
 import { CiCircleRemove } from 'react-icons/ci';
 import Select from 'react-select';
 import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
+import { Id } from '../../fields/FieldSymbols';
+import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
-import { DocOptions, DocumentOptions, FInfo } from '../documents/Documents';
+import { DocOptions, FInfo } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
 import { UserOptions } from '../util/GroupManager';
 import { SearchUtil } from '../util/SearchUtil';
+import { SettingsManager } from '../util/SettingsManager';
 import { undoable } from '../util/UndoManager';
 import './FilterPanel.scss';
 import { FieldView } from './nodes/FieldView';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
-import { SettingsManager } from '../util/SettingsManager';
-import { Id } from '../../fields/FieldSymbols';
-import { List } from '../../fields/List';
-import { emptyFunction } from '../../Utils';
 
 interface filterProps {
     Document: Doc;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 33cda6a62..65aadc148 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,15 +1,14 @@
-import * as React from 'react';
 import * as fitCurve from 'fit-curve';
-import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
+import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
 import { BoolCast, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
 import { GestureUtils } from '../../pen-gestures/GestureUtils';
 import { MobileInkOverlayContent } from '../../server/Message';
-import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { Transform } from '../util/Transform';
@@ -29,9 +28,9 @@ import {
     SetActiveInkColor,
     SetActiveInkWidth,
 } from './InkingStroke';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import { checkInksToGroup } from './global/globalScripts';
 import { DocumentView } from './nodes/DocumentView';
-import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface GestureOverlayProps {
     isActive: boolean;
@@ -48,22 +47,22 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
         return BoolCast(Doc.UserDoc().recognizeGestures);
     }
 
-    @observable public InkShape: Opt<GestureUtils.Gestures>;
-    @observable public SavedColor?: string;
-    @observable public SavedWidth?: number;
+    @observable public InkShape: Opt<GestureUtils.Gestures> = undefined;
+    @observable public SavedColor?: string = undefined;
+    @observable public SavedWidth?: number = undefined;
     @observable public Tool: ToolglassTools = ToolglassTools.None;
     @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
 
-    @observable private _thumbX?: number;
-    @observable private _thumbY?: number;
+    @observable private _thumbX?: number = undefined;
+    @observable private _thumbY?: number = undefined;
     @observable private _selectedIndex: number = -1;
     @observable private _menuX: number = -300;
     @observable private _menuY: number = -300;
-    @observable private _pointerY?: number;
+    @observable private _pointerY?: number = undefined;
     @observable private _points: { X: number; Y: number }[] = [];
     @observable private _strokes: InkData[] = [];
-    @observable private _palette?: JSX.Element;
-    @observable private _clipboardDoc?: JSX.Element;
+    @observable private _palette?: JSX.Element = undefined;
+    @observable private _clipboardDoc?: JSX.Element = undefined;
     @observable private _possibilities: JSX.Element[] = [];
 
     public static DownDocView: DocumentView | undefined;
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index b2ac208ca..7e2b334af 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -17,10 +17,10 @@
 // export class InkTranscription extends React.Component {
 //     static Instance: InkTranscription;
 
-//     @observable _mathRegister: any;
-//     @observable _mathRef: any;
-//     @observable _textRegister: any;
-//     @observable _textRef: any;
+//     @observable _mathRegister: any= undefined;
+//     @observable _mathRef: any= undefined;
+//     @observable _textRegister: any= undefined;
+//     @observable _textRef: any= undefined;
 //     private lastJiix: any;
 //     private currGroup?: Doc;
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 9c96b4563..95c677845 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -20,9 +20,9 @@
     
     Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
 */
-import * as React from 'react';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../fields/Doc';
 import { InkData, InkField } from '../../fields/InkField';
 import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
@@ -32,7 +32,6 @@ import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
-import { Transform } from '../util/Transform';
 import { UndoManager } from '../util/UndoManager';
 import { ContextMenu } from './ContextMenu';
 import { ViewBoxBaseComponent } from './DocComponent';
@@ -59,14 +58,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
     private _disposers: { [key: string]: IReactionDisposer } = {};
 
-    @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
-    @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+    @observable _nearestSeg?: number = undefined; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
+    @observable _nearestT?: number = undefined; // nearest t value within the nearest Bezier segment "
     @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke ""
 
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         this._disposers.selfDisper = reaction(
-            () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
+            () => this._props.isSelected(), // react to stroke being deselected by turning off ink handles
             selected => !selected && (InkStrokeProperties.Instance._controlButton = false)
         );
     }
@@ -76,7 +75,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
 
     // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
     // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1);
+    screenToLocal = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
@@ -100,27 +99,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.Document;
     };
 
-    /**
-     * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
-     * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
-     * the center of the ink stroke changes as the stroke is rotated.
-     */
-    getCenter = (xf: Transform) => {
-        const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const angle = -NumCast(this.layoutDoc.rotation);
-        const newPoints = inkData.map(pt => {
-            const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX;
-            const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y;
-            return { X: newX, Y: newY };
-        });
-        const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2;
-        const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2;
-        const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX;
-        const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry;
-        const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2);
-        return { X: tc[0], Y: tc[1] };
-    };
-
     /**
      * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field,
      * and the recognized words to the 'handwriting'
@@ -147,7 +125,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     onPointerDown = (e: React.PointerEvent) => {
         this._handledClick = false;
-        const inkView = this.props.docViewPath().lastElement();
+        const inkView = this._props.docViewPath().lastElement();
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
@@ -159,7 +137,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
         const controlIndex = nearestSeg;
         const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
-        const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
+        const isEditing = InkStrokeProperties.Instance._controlButton && this._props.isSelected();
         this.controlUndo = undefined;
         setupMoveUpEvents(
             this,
@@ -187,7 +165,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     InkStrokeProperties.Instance._currentPoint = -1;
                     this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
                     if (isEditing) {
-                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+                        this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this._props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
                     }
                 }
             }),
@@ -258,8 +236,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             inkLeft,
             inkWidth,
             inkHeight,
-            inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
-            inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
+            inkScaleX: (this._props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1,
+            inkScaleY: (this._props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1,
         };
     };
 
@@ -311,7 +289,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      * @returns the JSX controls for displaying an editing UI for the stroke (control point & tangent handles)
      */
     componentUI = (boundsLeft: number, boundsTop: number) => {
-        const inkDoc = this.props.Document;
+        const inkDoc = this.Document;
         const { inkData, inkStrokeWidth } = this.inkScaledData();
         const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
 
@@ -321,7 +299,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
         const markerScale = NumCast(this.layoutDoc.stroke_markerScale);
         return SnappingManager.IsDragging ? null : !InkStrokeProperties.Instance._controlButton ? (
-            !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
+            !this._props.isSelected() || InkingStroke.IsClosed(inkData) ? null : (
                 <div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
                     <InkEndPtHandles inkView={this} inkDoc={inkDoc} startPt={this.startPt} endPt={this.endPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
                 </div>
@@ -360,12 +338,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     setSubContentView = (doc: DocComponentView) => (this._subContentView = doc);
     @computed get fillColor() {
         const isInkMask = BoolCast(this.layoutDoc.stroke_isInkMask);
-        return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FillColor) ?? 'transparent';
+        return isInkMask ? DashColor(StrCast(this.layoutDoc.fillColor, 'transparent')).blacken(0).rgb().toString() : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FillColor) ?? 'transparent';
     }
     @computed get strokeColor() {
         const { inkData } = this.inkScaledData();
         const fillColor = this.fillColor;
-        return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
+        return !InkingStroke.IsClosed(inkData) && fillColor && fillColor !== 'transparent' ? fillColor : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) ?? StrCast(this.layoutDoc.color);
     }
     render() {
         TraceMobx();
@@ -385,9 +363,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.layoutDoc._height = Math.round(NumCast(this.layoutDoc._height));
             });
         }
-        const highlight = !this.controlUndo && this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
+        const highlight = !this.controlUndo && this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
         const highlightIndex = highlight?.highlightIndex;
-        const highlightColor = !this.props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+        const highlightColor = !this._props.isSelected() && !isInkMask && highlight?.highlightIndex ? highlight?.highlightColor : undefined;
         const color = StrCast(this.layoutDoc.stroke_outlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent');
 
         // Visually renders the polygonal line made by the user.
@@ -437,7 +415,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 inkScaleX,
                 inkScaleY,
                 '',
-                this.props.pointerEvents?.() ?? 'visiblepainted',
+                this._props.pointerEvents?.() ?? 'visiblepainted',
                 0.0,
                 false,
                 downHdlr,
@@ -450,7 +428,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', '');
         const interactions = {
             onPointerLeave: action(() => (this._nearestScrPt = undefined)),
-            onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined,
+            onPointerMove: this._props.isSelected() ? this.onPointerMove : undefined,
             onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(),
             onContextMenu: () => {
                 const cm = ContextMenu.Instance;
@@ -464,27 +442,27 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                 <svg
                     className="inkStroke"
                     style={{
-                        transform: isInkMask ? `rotate(-${NumCast(this.props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+                        transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
                         // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
-                        cursor: this.props.isSelected() ? 'default' : undefined,
+                        cursor: this._props.isSelected() ? 'default' : undefined,
                     }}
                     {...interactions}>
                     {clickableLine(this.onPointerDown, isInkMask)}
                     {isInkMask ? null : inkLine}
                 </svg>
-                {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this.props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
+                {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
                     <div
                         className="inkStroke-text"
                         style={{
                             color: StrCast(this.layoutDoc.textColor, 'black'),
-                            pointerEvents: this.props.isDocumentActive?.() ? 'all' : undefined,
+                            pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
                             width: NumCast(this.layoutDoc._width),
-                            transform: `scale(${this.props.NativeDimScaling?.() || 1})`,
+                            transform: `scale(${this._props.NativeDimScaling?.() || 1})`,
                             transformOrigin: 'top left',
-                            //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
+                            //top: (this._props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this._props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
-                            {...this.props}
+                            {...this._props}
                             setHeight={undefined}
                             setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
@@ -493,7 +471,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             dontRegisterView={true}
                             noSidebar={true}
                             dontScale={true}
-                            isContentActive={this.props.isContentActive}
+                            isContentActive={this._props.isContentActive}
                         />
                     </div>
                 )}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 094bd2adb..35ffab337 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -118,7 +118,7 @@ export class MainView extends ObservableReactComponent<{}> {
     @computed private get userDoc() {
         return Doc.UserDoc();
     }
-    @observable mainDoc: Opt<Doc>;
+    @observable mainDoc: Opt<Doc> = undefined;
     @computed private get mainContainer() {
         if (window.location.pathname.startsWith('/doc/') && Doc.CurrentUserEmail === 'guest') {
             DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc)));
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 5c6912121..89c3c41f8 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
-import './MetadataEntryMenu.scss';
+import { IReactionDisposer, action, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCastAsync, DocListCast } from '../../fields/Doc';
+import * as React from 'react';
 import * as Autosuggest from 'react-autosuggest';
-import { undoBatch, UndoManager } from '../util/UndoManager';
 import { emptyFunction, emptyPath } from '../../Utils';
+import { Doc, DocListCast, Field } from '../../fields/Doc';
+import { undoBatch } from '../util/UndoManager';
+import './MetadataEntryMenu.scss';
+import { KeyValueBox } from './nodes/KeyValueBox';
 
 export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
 export interface MetadataEntryProps {
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 166afc0c2..a69a5f5e8 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -24,7 +24,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
     _addLiveTextDoc?: (doc: Doc) => void;
     _nudge?: undefined | ((x: number, y: number) => boolean);
     _slowLoadDocuments?: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
-    @observable _clickPoint: number[];
+    @observable _clickPoint: number[] = [];
     @observable public Visible = false;
     public Doc: Opt<Doc>;
     constructor(props: any) {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 8540e81e1..f1dadcd5e 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -2,6 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Dropdown, DropdownType, IListItemProps, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { AiOutlineColumnWidth } from 'react-icons/ai';
 import { BiHide, BiShow } from 'react-icons/bi';
 import { BsGrid3X3GapFill } from 'react-icons/bs';
@@ -12,28 +13,20 @@ import { RxWidth } from 'react-icons/rx';
 import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb';
 import { TfiBarChart } from 'react-icons/tfi';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
-import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { BoolCast, ScriptCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
-import { DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocUtils } from '../documents/Documents';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
-import { undoable, undoBatch } from '../util/UndoManager';
-import { Colors } from './global/globalEnums';
+import { undoBatch, undoable } from '../util/UndoManager';
 import { InkingStroke } from './InkingStroke';
-import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import './PropertiesButtons.scss';
-import * as React from 'react';
-
-enum UtilityButtonState {
-    Default,
-    OpenRight,
-    OpenExternally,
-}
+import { Colors } from './global/globalEnums';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
 
 @observer
 export class PropertiesButtons extends React.Component<{}, {}> {
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 58d3b72e8..244cd4aa0 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -7,10 +7,10 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
 import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
+import './PropertiesDocBacklinksSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { LinkMenu } from './linking/LinkMenu';
-import { OpenWhere, OpenWhereMod } from './nodes/DocumentView';
-import './PropertiesDocBacklinksSelector.scss';
+import { OpenWhere } from './nodes/DocumentView';
 
 type PropertiesDocBacklinksSelectorProps = {
     Document: Doc;
diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx
index 2b9bfb505..3c9fa1123 100644
--- a/src/client/views/PropertiesSection.tsx
+++ b/src/client/views/PropertiesSection.tsx
@@ -1,11 +1,9 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import './PropertiesSection.scss';
-import { Doc } from '../../fields/Doc';
-import { StrCast } from '../../fields/Types';
+import * as React from 'react';
 import { SettingsManager } from '../util/SettingsManager';
+import './PropertiesSection.scss';
 
 export interface PropertiesSectionProps {
     title: string;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 48653f30a..791928b4a 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,5 +1,6 @@
 import { action, computed, observable, ObservableSet, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, StrCast } from '../../fields/Types';
@@ -13,7 +14,6 @@ import { CollectionTreeView } from './collections/CollectionTreeView';
 import { DocumentView } from './nodes/DocumentView';
 import { DefaultStyleProvider } from './StyleProvider';
 import './TemplateMenu.scss';
-import * as React from 'react';
 
 @observer
 class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent<HTMLInputElement>, template: string) => void }> {
diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx
index f07e38af1..ea038250e 100644
--- a/src/client/views/UndoStack.tsx
+++ b/src/client/views/UndoStack.tsx
@@ -15,8 +15,8 @@ interface UndoStackProps {
 }
 @observer
 export class UndoStack extends React.Component<UndoStackProps> {
-    @observable static HideInline: boolean;
-    @observable static Expand: boolean;
+    @observable static HideInline: boolean = false;
+    @observable static Expand: boolean = false;
     render() {
         const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor;
         const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor;
@@ -39,19 +39,25 @@ export class UndoStack extends React.Component<UndoStackProps> {
                                         color,
                                     }}>
                                     {Array.from(UndoManager.undoStackNames).map((name, i) => (
-                                        <div className="undoStack-resultContainer" key={i} 
-                                             onClick={e => {
+                                        <div
+                                            className="undoStack-resultContainer"
+                                            key={i}
+                                            onClick={e => {
                                                 const size = UndoManager.undoStackNames.length;
-                                                for (let n = 0; n < size-i; n++ ) UndoManager.Undo();  } } 
-                                            >
+                                                for (let n = 0; n < size - i; n++) UndoManager.Undo();
+                                            }}>
                                             <div className="undoStack-commandString">{StrCast(name).replace(/[^\.]*\./, '')}</div>
                                         </div>
                                     ))}
                                     {Array.from(UndoManager.redoStackNames)
                                         .reverse()
                                         .map((name, i) => (
-                                            <div className="undoStack-resultContainer" key={i} onClick={e =>
-                                                { for (let n = 0; n <= i; n++ ) UndoManager.Redo() }}>
+                                            <div
+                                                className="undoStack-resultContainer"
+                                                key={i}
+                                                onClick={e => {
+                                                    for (let n = 0; n <= i; n++) UndoManager.Redo();
+                                                }}>
                                                 <div className="undoStack-commandString" style={{ fontWeight: 'bold', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
                                                     {StrCast(name).replace(/[^\.]*\./, '')}
                                                 </div>
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index 15cbbc16f..99163f6c6 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -6,11 +6,11 @@ import { List } from '../../../fields/List';
 import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import '../global/globalCssVariables.module.scss';
 import './Region.scss';
 import './Timeline.scss';
 import { TimelineMenu } from './TimelineMenu';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 /**
  * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f27a2d2fd..cc4da1694 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -4,17 +4,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DocumentType } from '../../documents/DocumentTypes';
 import clamp from '../../util/clamp';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { FieldViewProps } from '../nodes/FieldView';
 import { RegionHelpers } from './Region';
 import './Timeline.scss';
 import { TimelineOverview } from './TimelineOverview';
 import { Track } from './Track';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 /**
  * Timeline class controls most of timeline functions besides individual region and track mechanism. Main functions are 
diff --git a/src/client/views/animationtimeline/TimelineOverview.tsx b/src/client/views/animationtimeline/TimelineOverview.tsx
index 82ac69a3b..928739b53 100644
--- a/src/client/views/animationtimeline/TimelineOverview.tsx
+++ b/src/client/views/animationtimeline/TimelineOverview.tsx
@@ -28,7 +28,7 @@ export class TimelineOverview extends React.Component<TimelineOverviewProps> {
     @observable private overviewBarWidth: number = 0;
     @observable private playbarWidth: number = 0;
     @observable private activeOverviewWidth: number = 0;
-    @observable private _authoringReaction?: IReactionDisposer;
+    @observable private _authoringReaction?: IReactionDisposer = undefined;
     @observable private visibleTime: number = 0;
     @observable private currentX: number = 0;
     @observable private visibleStart: number = 0;
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 00aa51cac..490a14be5 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -8,10 +8,10 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast } from '../../../fields/Types';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { Region, RegionData, RegionHelpers } from './Region';
 import { Timeline } from './Timeline';
 import './Track.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 interface IProps {
     timeline: Timeline;
@@ -29,9 +29,9 @@ interface IProps {
 @observer
 export class Track extends ObservableReactComponent<IProps> {
     @observable private _inner = React.createRef<HTMLDivElement>();
-    @observable private _currentBarXReaction: any;
-    @observable private _timelineVisibleReaction: any;
-    @observable private _autoKfReaction: any;
+    @observable private _currentBarXReaction: any = undefined;
+    @observable private _timelineVisibleReaction: any = undefined;
+    @observable private _autoKfReaction: any = undefined;
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
     @observable private _trackHeight = 0;
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 2cec29669..4c0a917f5 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,18 +1,11 @@
-import * as React from 'react';
-import { CollectionSubView } from './CollectionSubView';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
-import { computed, makeObservable, observable } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import * as React from 'react';
+import { dateRangeStrToDates, emptyFunction, returnTrue } from '../../../Utils';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
 import { CollectionStackingView } from './CollectionStackingView';
-import { CollectionViewType } from '../../documents/DocumentTypes';
-import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
-import { TbRuler } from 'react-icons/tb';
-import { Transform } from '../../util/Transform';
-import { DocData } from '../../../fields/DocSymbols';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { CollectionSubView } from './CollectionSubView';
 
 @observer
 export class CollectionCalendarView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 75c178136..868df7a3b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
@@ -30,7 +30,7 @@ import { ScriptingRepl } from '../ScriptingRepl';
 import { UndoStack } from '../UndoStack';
 import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 443aa3f17..2a6cb081f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -32,16 +32,13 @@ interface CollectionMenuProps {
 @observer
 export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
     @observable static Instance: CollectionMenu;
-
     @observable SelectedCollection: DocumentView | undefined = undefined;
-    @observable FieldKey: string;
 
     private _docBtnRef = React.createRef<HTMLDivElement>();
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
-        this.FieldKey = '';
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
         this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true);
diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
index af822d917..2aad48d4a 100644
--- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx
@@ -1,4 +1,4 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 170b1f1ec..e5068645f 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,5 +1,6 @@
 import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
 import { NumCast, StrCast } from '../../../fields/Types';
@@ -12,7 +13,6 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import './CollectionPileView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import * as React from 'react';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index fb8bc4da2..fa3844aa4 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -20,7 +20,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
+import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
@@ -56,7 +56,7 @@ export enum TrimScope {
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
     @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
-    @observable public static CurrentlyPlaying: DocumentView[];
+    @observable public static CurrentlyPlaying: DocumentView[] = [];
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index f9d575da2..69f1c332d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -20,8 +20,8 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import './CollectionStackingView.scss';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import './CollectionStackingView.scss';
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2ff435ce2..570330174 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,11 +4,11 @@ import * as rp from 'request-promise';
 import { Utils, returnFalse } from '../../../Utils';
 import CursorField from '../../../fields/CursorField';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, DocData } from '../../../fields/DocSymbols';
+import { AclPrivate } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -41,8 +41,8 @@ export function CollectionSubView<X>(moreProps?: X) {
             makeObservable(this);
         }
 
-        @observable _focusFilters: Opt<string[]>; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
-        @observable _focusRangeFilters: Opt<string[]>; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
+        @observable _focusFilters: Opt<string[]> = undefined; // childFilters that are overridden when previewing a link to an anchor which has childFilters set on it
+        @observable _focusRangeFilters: Opt<string[]> = undefined; // childFiltersByRanges that are overridden when previewing a link to an anchor which has childFiltersByRanges set on it
         protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
             this.dropDisposer?.();
             this.gestureDisposer?.();
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 6033b897d..7bbdb7073 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,6 +1,8 @@
 import { toUpper } from 'lodash';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
@@ -8,7 +10,6 @@ import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -17,20 +18,19 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { DocFocusOptions, DocumentView } from '../nodes/DocumentView';
 import { PresBox } from '../nodes/trails';
-import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionSubView } from './CollectionSubView';
 import './CollectionTimeView.scss';
-import * as React from 'react';
+import { ViewDefBounds, computePivotLayout, computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 
 @observer
 export class CollectionTimeView extends CollectionSubView() {
     _changing = false;
     @observable _layoutEngine = computePivotLayout.name;
     @observable _collapsed: boolean = false;
-    @observable _childClickedScript: Opt<ScriptField>;
-    @observable _viewDefDivClick: Opt<ScriptField>;
-    @observable _focusPivotField: Opt<string>;
+    @observable _childClickedScript: Opt<ScriptField> = undefined;
+    @observable _viewDefDivClick: Opt<ScriptField> = undefined;
+    @observable _focusPivotField: Opt<string> = undefined;
 
     constructor(props: any) {
         super(props);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0656843cb..c2f5ab2c0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -16,6 +16,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from './CollectionDockingView';
@@ -28,7 +29,6 @@ import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionCalendarView } from './CollectionCalendarView';
 import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index f3071b316..4523a4f1e 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -1,6 +1,6 @@
-import * as React from "react";
-import { observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
+import { observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 
 interface IKeyRestrictionProps {
     contains: boolean;
@@ -19,37 +19,28 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
         if (this._key && this._value) {
             let parsedValue: string | number = `"${this._value}"`;
             const parsed = parseInt(this._value);
-            let type = "string";
+            let type = 'string';
             if (!isNaN(parsed)) {
                 parsedValue = parsed;
-                type = "number";
+                type = 'number';
             }
-            const scriptText = `${this._contains ? "" : "!"}(((doc.${this._key} && (doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))) ||
-                ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))))`;
+            const scriptText = `${this._contains ? '' : '!'}(((doc.${this._key} && (doc.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))) ||
+                ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))))`;
             // let doc = new Doc();
             // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello"));
             this.props.script(scriptText);
-        }
-        else {
-            this.props.script("");
+        } else {
+            this.props.script('');
         }
 
         return (
             <div className="collectionViewBaseChrome-viewSpecsMenu-row">
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"
-                    value={this._key}
-                    onChange={(e) => runInAction(() => this._key = e.target.value)}
-                    placeholder="KEY" />
-                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
-                    style={{ background: this._contains ? "#77dd77" : "#ff6961" }}
-                    onClick={() => runInAction(() => this._contains = !this._contains)}>
-                    {this._contains ? "CONTAINS" : "DOES NOT CONTAIN"}
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" value={this._key} onChange={e => runInAction(() => (this._key = e.target.value))} placeholder="KEY" />
+                <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" style={{ background: this._contains ? '#77dd77' : '#ff6961' }} onClick={() => runInAction(() => (this._contains = !this._contains))}>
+                    {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'}
                 </button>
-                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
-                    value={this._value}
-                    onChange={(e) => runInAction(() => this._value = e.target.value)}
-                    placeholder="VALUE" />
+                <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" value={this._value} onChange={e => runInAction(() => (this._value = e.target.value))} placeholder="VALUE" />
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a7705ea7e..72882ac17 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -93,7 +93,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     static _openLevelScript: Opt<ScriptField | undefined>;
     private _header: React.RefObject<HTMLDivElement> = React.createRef();
     private _tref = React.createRef<HTMLDivElement>();
-    @observable _docRef: Opt<DocumentView>;
+    @observable _docRef: Opt<DocumentView> = undefined;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _editTitleScript: (() => ScriptField) | undefined;
     private _openScript: (() => ScriptField) | undefined;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
index 99ee5ef4e..08dfb32ad 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -1,8 +1,8 @@
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast } from '../../../../fields/Types';
 import './CollectionFreeFormView.scss';
-import * as React from 'react';
 
 export interface CollectionFreeFormViewBackgroundGridProps {
     panX: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index 2606304d0..58f6b1593 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,5 +1,5 @@
 import { IconButton, Size, Type } from 'browndash-components';
-import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 15e4d8360..8628ca3c3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,16 +1,16 @@
-import { IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
+import { makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Field, FieldResult } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
+import { StrCast } from '../../../../fields/Types';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { LinkManager } from '../../../util/LinkManager';
-import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { CollectionFreeFormInfoState, InfoState, infoState, StateEntryFunc } from './CollectionFreeFormInfoState';
+import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
 import './CollectionFreeFormView.scss';
-import { StrCast } from '../../../../fields/Types';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 779a0bf96..95d521f65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,11 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Id } from '../../../../fields/FieldSymbols';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { LightboxView } from '../../LightboxView';
-import './CollectionFreeFormLinksView.scss';
 import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
-import * as React from 'react';
+import './CollectionFreeFormLinksView.scss';
 
 @observer
 export class CollectionFreeFormLinksView extends React.Component {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index f54726a00..ec8416303 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -1,12 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { PresBox } from '../../nodes/trails/PresBox';
-import './CollectionFreeFormView.scss';
-import * as React from 'react';
 import { CollectionFreeFormView } from './CollectionFreeFormView';
-
+import './CollectionFreeFormView.scss';
 export interface CollectionFreeFormPannableContentsProps {
     Document: Doc;
     viewDefDivClick?: ScriptField;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 45e24bbb2..fa8218bdd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,6 +1,8 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as mobxUtils from 'mobx-utils';
+import * as React from 'react';
+import * as uuid from 'uuid';
 import CursorField from '../../../../fields/CursorField';
 import { Doc, FieldResult } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -9,8 +11,6 @@ import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { CollectionViewProps } from '../CollectionView';
 import './CollectionFreeFormView.scss';
-import * as React from 'react';
-import * as uuid from 'uuid';
 
 @observer
 export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 645e9cff7..cc0833698 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,6 +37,7 @@ import { CtrlKey } from '../../GlobalKeyHandler';
 import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
+import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -52,7 +53,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
-import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
 
 export type collectionFreeformViewProps = {
     NativeWidth?: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b22fdfa19..39d828302 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -16,19 +16,18 @@ import { DocumentType } from '../../../documents/DocumentTypes';
 import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { freeformScrollMode } from '../../../util/SettingsManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { UndoManager, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { PreviewCursor } from '../../PreviewCursor';
-import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
+import { OpenWhere } from '../../nodes/DocumentView';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { SubCollectionViewProps } from '../CollectionSubView';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
-import { SnappingManager } from '../../../util/SnappingManager';
-
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
     getTransform: () => Transform;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 1e19964d7..3e75257e5 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -7,7 +7,6 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types
 import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
-import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
@@ -17,13 +16,12 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionGridView.scss';
 import Grid, { Layout } from './Grid';
-
 @observer
 export class CollectionGridView extends CollectionSubView() {
     private _containerRef: React.RefObject<HTMLDivElement> = React.createRef();
     private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs
     private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
-    @observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
+    @observable private _rowHeight: Opt<number> = undefined; // temporary store of row height to make change undoable
     @observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
     private dropLocation: object = {}; // sets the drop location for external drops
 
diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx
index 3d1d87aa0..9145d7ef1 100644
--- a/src/client/views/collections/collectionGrid/Grid.tsx
+++ b/src/client/views/collections/collectionGrid/Grid.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 
 import '../../../../../node_modules/react-grid-layout/css/styles.css';
 import '../../../../../node_modules/react-resizable/css/styles.css';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index e12bcd8b0..5bea59e7b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -4,13 +4,13 @@ import { Button } from 'browndash-components';
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch, undoable } from '../../../util/UndoManager';
 import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 7dbc18e60..3043eb0f8 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,9 +1,9 @@
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
@@ -12,7 +12,6 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
 import ResizeBar from './MultirowResizer';
-
 interface HeightSpecifier {
     magnitude: number;
     unit: string;
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index 868b1140d..ea99bff2e 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { DimUnit } from './CollectionMulticolumnView';
 import { UndoManager } from '../../../util/UndoManager';
-import { StyleProviderFunc } from '../../nodes/DocumentView';
 import { StyleProp } from '../../StyleProvider';
+import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { DimUnit } from './CollectionMulticolumnView';
 
 interface ResizerProps {
     width: number;
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
index 9985a9fba..a9579d931 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx
@@ -1,27 +1,25 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
-import { EditableView } from "../../EditableView";
-import { DimUnit } from "./CollectionMulticolumnView";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../../fields/Doc';
+import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
+import { EditableView } from '../../EditableView';
+import { DimUnit } from './CollectionMulticolumnView';
 
 interface WidthLabelProps {
     layout: Doc;
     collectionDoc: Doc;
-    decimals?: number;
 }
 
 @observer
 export default class WidthLabel extends React.Component<WidthLabelProps> {
-
     @computed
     private get contents() {
-        const { layout, decimals } = this.props;
+        const { layout } = this.props;
         const getUnit = () => StrCast(layout.dimUnit);
-        const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
+        const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(3));
         return (
-            <div className={"label-wrapper"}>
+            <div className={'label-wrapper'}>
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
@@ -50,7 +48,6 @@ export default class WidthLabel extends React.Component<WidthLabelProps> {
     }
 
     render() {
-        return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : (null);
+        return BoolCast(this.props.collectionDoc.showWidthLabels) ? this.contents : null;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
index aa5439fa4..878c7ff3c 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx
@@ -1,10 +1,10 @@
-import * as React from "react";
-import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Doc } from "../../../../fields/Doc";
-import { NumCast, StrCast, BoolCast } from "../../../../fields/Types";
-import { EditableView } from "../../EditableView";
-import { DimUnit } from "./CollectionMultirowView";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc } from '../../../../fields/Doc';
+import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
+import { EditableView } from '../../EditableView';
+import { DimUnit } from './CollectionMultirowView';
 
 interface HeightLabelProps {
     layout: Doc;
@@ -14,14 +14,13 @@ interface HeightLabelProps {
 
 @observer
 export default class HeightLabel extends React.Component<HeightLabelProps> {
-
     @computed
     private get contents() {
         const { layout, decimals } = this.props;
         const getUnit = () => StrCast(layout.dimUnit);
         const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3));
         return (
-            <div className={"label-wrapper"}>
+            <div className={'label-wrapper'}>
                 <EditableView
                     GetValue={getMagnitude}
                     SetValue={value => {
@@ -50,7 +49,6 @@ export default class HeightLabel extends React.Component<HeightLabelProps> {
     }
 
     render() {
-        return BoolCast(this.props.collectionDoc.showHeightLabels) ? this.contents : (null);
+        return BoolCast(this.props.collectionDoc.showHeightLabels) ? this.contents : null;
     }
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 5a9d6a82c..7dee65e58 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { DimUnit } from './CollectionMultirowView';
 import { UndoManager } from '../../../util/UndoManager';
 import { StyleProp } from '../../StyleProvider';
 import { StyleProviderFunc } from '../../nodes/DocumentView';
+import { DimUnit } from './CollectionMultirowView';
 
 interface ResizerProps {
     height: number;
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index 04443b4a7..5f8b412be 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Colors } from '../../global/globalEnums';
 import './CollectionSchemaView.scss';
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 06073b52c..85e97f95f 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,3 +1,4 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
@@ -7,18 +8,17 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
+import { undoBatch } from '../../util/UndoManager';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
-import { undoBatch } from '../../util/UndoManager';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuItemProps {
     groupType: string;
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index 5460a6daf..7e344dd7a 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -1,16 +1,16 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { EditorView } from 'prosemirror-view';
-import { Doc } from '../../../fields/Doc';
+import * as React from 'react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
+import { DefaultStyleProvider } from '../StyleProvider';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { SearchBox } from '../search/SearchBox';
-import { DefaultStyleProvider } from '../StyleProvider';
 import './LinkPopup.scss';
-import * as React from 'react';
 
 interface LinkPopupProps {
     linkFrom?: () => Doc | undefined;
@@ -29,7 +29,7 @@ interface LinkPopupProps {
 @observer
 export class LinkPopup extends React.Component<LinkPopupProps> {
     @observable private linkURL: string = '';
-    @observable public view?: EditorView;
+    @observable public view?: EditorView = undefined;
 
     // TODO: should check for valid URL
     @undoBatch
diff --git a/src/client/views/linking/LinkRelationshipSearch.tsx b/src/client/views/linking/LinkRelationshipSearch.tsx
index 3e7f5be74..0902d53b2 100644
--- a/src/client/views/linking/LinkRelationshipSearch.tsx
+++ b/src/client/views/linking/LinkRelationshipSearch.tsx
@@ -1,6 +1,6 @@
 import { observer } from 'mobx-react';
-import './LinkEditor.scss';
 import * as React from 'react';
+import './LinkEditor.scss';
 
 interface link_relationshipSearchProps {
     results: string[] | undefined;
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
index 72b63cf8f..bce2b296f 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx
@@ -3,12 +3,12 @@ import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { SelectionManager } from '../../../util/SelectionManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { DocumentView, OpenWhereMod } from '../../nodes/DocumentView';
+import { OpenWhereMod } from '../../nodes/DocumentView';
 import { NewLightboxView } from '../NewLightboxView';
 import './ButtonMenu.scss';
 import { IButtonMenu } from './utils';
-import { SnappingManager } from '../../../util/SnappingManager';
 
 export const ButtonMenu = (props: IButtonMenu) => {
     return (
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 6980e31c1..3d159c3e3 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -2,28 +2,28 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
-import { TabDocView } from '../collections/TabDocView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { DocumentView, OpenWhere } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { IRecommendation } from './components';
+import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
+import { TabDocView } from '../collections/TabDocView';
+import { DocumentView, OpenWhere } from '../nodes/DocumentView';
 import { ExploreView } from './ExploreView';
-import { emptyBounds, IBounds } from './ExploreView/utils';
+import { IBounds, emptyBounds } from './ExploreView/utils';
 import { NewLightboxHeader } from './Header';
 import './NewLightboxView.scss';
 import { RecommendationList } from './RecommendationList';
-import { SnappingManager } from '../../util/SnappingManager';
+import { IRecommendation } from './components';
 
 enum LightboxStatus {
     RECOMMENDATIONS = 'recommendations',
@@ -50,15 +50,15 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
         return this._doc;
     }
     private static LightboxDocTemplate = () => NewLightboxView._layoutTemplate;
-    @observable private static _layoutTemplate: Opt<Doc>;
-    @observable private static _layoutTemplateString: Opt<string>;
-    @observable private static _doc: Opt<Doc>;
-    @observable private static _docTarget: Opt<Doc>;
+    @observable private static _layoutTemplate: Opt<Doc> = undefined;
+    @observable private static _layoutTemplateString: Opt<string> = undefined;
+    @observable private static _doc: Opt<Doc> = undefined;
+    @observable private static _docTarget: Opt<Doc> = undefined;
     @observable private static _docFilters: string[] = []; // filters
-    private static _savedState: Opt<LightboxSavedState>;
+    private static _savedState: Opt<LightboxSavedState> = undefined;
     private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
     @observable private static _future: Opt<Doc[]> = [];
-    @observable private static _docView: Opt<DocumentView>;
+    @observable private static _docView: Opt<DocumentView> = undefined;
 
     // keywords
     @observable private static _keywords: string[] = [];
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 4c36d2fcb..12e8e1a69 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -44,8 +44,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
-    @observable schemaDataVizChildren: any;
-    @observable _marqueeing: number[] | undefined;
+    @observable schemaDataVizChildren: any = undefined;
+    @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get annotationLayer() {
         TraceMobx();
diff --git a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
index 3cb5125da..24023077f 100644
--- a/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
+++ b/src/client/views/nodes/DataVizBox/SchemaCSVPopUp.tsx
@@ -1,15 +1,14 @@
-import * as React from 'react';
-import './SchemaCSVPopUp.scss';
+import { IconButton } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { CgClose } from 'react-icons/cg';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { Button, IconButton, Type } from 'browndash-components';
 import { StrCast } from '../../../../fields/Types';
-import { MarqueeView } from '../../collections/collectionFreeForm/MarqueeView';
-import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { DocumentView } from '../DocumentView';
-import { CgClose } from 'react-icons/cg';
+import './SchemaCSVPopUp.scss';
 
 interface SchemaCSVPopUpProps {}
 
@@ -56,13 +55,11 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
             <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                 {this.heading('Schema Table as Data Visualization Doc')}
                 <div className="image-content-wrapper">
-                        <div className="img-wrapper">
-                            <div className="img-container" onPointerDown={e => this.drag(e)}>
-                                <img
-                                    width={150} height={150} src={"/assets/dataVizBox.png"}
-                                />
-                            </div>
+                    <div className="img-wrapper">
+                        <div className="img-container" onPointerDown={e => this.drag(e)}>
+                            <img width={150} height={150} src={'/assets/dataVizBox.png'} />
                         </div>
+                    </div>
                 </div>
             </div>
         );
@@ -88,9 +85,10 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
                     return embedding;
                 };
                 if (this.view && sourceAnchorCreator && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
-                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], 
-                        new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
-                        dragComplete: e => {this.setVisible(false);},
+                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.view, sourceAnchorCreator, targetCreator), downX, downY, {
+                        dragComplete: e => {
+                            this.setVisible(false);
+                        },
                     });
                     return true;
                 }
@@ -108,4 +106,4 @@ export class SchemaCSVPopUp extends React.Component<SchemaCSVPopUpProps> {
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 227c993c7..9e9a43b34 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -11,10 +11,10 @@ import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface HistogramProps {
     Document: Doc;
@@ -461,7 +461,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
 
         if (this._histogramData.length > 0 || !this.parentViz) {
             return this._props.axes.length >= 1 ? (
-                <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
+                <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 4b0df0457..a69f083d1 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -1,6 +1,6 @@
 import { Button, EditableText, Size } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc';
@@ -10,11 +10,11 @@ import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { DocumentManager } from '../../../../util/DocumentManager';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import { DataVizBox } from '../DataVizBox';
 import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface DataPoint {
     x: number;
@@ -48,12 +48,11 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
     private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
     @observable _currSelected: SelectedDataPoint | undefined = undefined;
     // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
-    constructor(props:any) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-
     @computed get _tableDataIds() {
         return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
     }
@@ -359,7 +358,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
         if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
             return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
-                <div className="chart-container" style={{width: this.props.width+this.props.margin.right}}>
+                <div className="chart-container" style={{ width: this.props.width + this.props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
@@ -373,16 +372,17 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
                         />
                     </div>
                     <div ref={this._lineChartRef} />
-                    {selectedPt != 'none' ? 
-                        <div className={'selected-data'}> 
+                    {selectedPt != 'none' ? (
+                        <div className={'selected-data'}>
                             {`Selected: ${selectedPt}`}
-                            <Button onClick={e=>{
-                                console.log("test plzz")
-                                this.props.vizBox.sidebarBtnDown;
-                                this.props.vizBox.sidebarAddDocument;}
-                                }></Button>
-                        </div> 
-                        : null}
+                            <Button
+                                onClick={e => {
+                                    console.log('test plzz');
+                                    this.props.vizBox.sidebarBtnDown;
+                                    this.props.vizBox.sidebarAddDocument;
+                                }}></Button>
+                        </div>
+                    ) : null}
                 </div>
             ) : (
                 <span className="chart-container"> {'first use table view to select two numerical axes to plot'}</span>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index e644870da..e67556cd0 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -1,7 +1,7 @@
 import { Checkbox } from '@mui/material';
 import { ColorPicker, EditableText, Size, Type } from 'browndash-components';
 import * as d3 from 'd3';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { FaFillDrip } from 'react-icons/fa';
@@ -11,9 +11,9 @@ import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast, StrCast } from '../../../../../fields/Types';
 import { Docs } from '../../../../documents/Documents';
 import { undoable } from '../../../../util/UndoManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { PinProps, PresBox } from '../../trails';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 
 export interface PieChartProps {
     Document: Doc;
@@ -356,7 +356,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
 
         if (this._pieChartData.length > 0 || !this.parentViz) {
             return this._props.axes.length >= 1 ? (
-                <div className="chart-container" style={{width: this._props.width+this._props.margin.right}}>
+                <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 4f60a156b..f127fecf3 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,17 +1,17 @@
 import { Button, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../../Utils';
 import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
 import { List } from '../../../../../fields/List';
 import { listSpec } from '../../../../../fields/Schema';
 import { Cast, DocCast } from '../../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
 import { DragManager } from '../../../../util/DragManager';
+import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { DocumentView } from '../../DocumentView';
 import { DataVizView } from '../DataVizBox';
 import './Chart.scss';
-import { ObservableReactComponent } from '../../../ObservableReactComponent';
 const {  default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
 interface TableBoxProps {
     Document: Doc;
@@ -87,7 +87,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
         return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
     }
     @computed get startID() {
-        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight)-1, 0) : 0;
+        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
     }
     @computed get endID() {
         console.log('start = ' + this.startID + '  container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
@@ -169,7 +169,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             return (
                 <div
                     className="tableBox"
-                    style={{width: this.props.width+this.props.margin.right}}
+                    style={{ width: this.props.width + this.props.margin.right }}
                     tabIndex={0}
                     onKeyDown={e => {
                         if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
@@ -226,7 +226,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                             </thead>
                             <tbody>
                                 {this._tableDataIds
-                                    .filter((rowId, i) => this.startID-2 <= i && i <= this.endID+2)
+                                    .filter((rowId, i) => this.startID - 2 <= i && i <= this.endID + 2)
                                     ?.map(rowId => (
                                         <tr
                                             key={rowId}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fa472312e..4cc7fe4c8 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,6 +1,7 @@
-import { computed, makeObservable, observable } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import JsxParser from 'react-jsx-parser';
 import * as XRegExp from 'xregexp';
 import { OmitKeys, Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
@@ -10,11 +11,11 @@ import { Cast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
+import { CollectionCalendarView } from '../collections/CollectionCalendarView';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionView } from '../collections/CollectionView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
-import { CollectionCalendarView } from '../collections/CollectionCalendarView';
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
 import { PresElementBox } from '../nodes/trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
@@ -47,7 +48,6 @@ import { WebBox } from './WebBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { ImportElementBox } from './importBox/ImportElementBox';
 import { PresBox } from './trails/PresBox';
-import JsxParser from 'react-jsx-parser';
 
 type BindingProps = Without<FieldViewProps, 'fieldKey'>;
 export interface JsxBindings {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cc7fca8d2..f8f4b94a2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,10 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
+import { Howl } from 'howler';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
-import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import * as React from 'react';
+import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
 import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
@@ -53,7 +54,6 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
-import { Howl } from 'howler';
 
 interface Window {
     MediaRecorder: MediaRecorder;
@@ -138,7 +138,6 @@ export interface DocComponentView {
     dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
     incrementalRendering?: () => void;
     infoUI?: () => JSX.Element | null;
-    getCenter?: (xf: Transform) => { X: number; Y: number };
     screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
@@ -1516,7 +1515,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
             const docuBox = this.docView.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
             if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
         }
-        return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
+        return { left, top, right, bottom };
     };
 
     public iconify(finished?: () => void, animateTime?: number) {
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 8d03bf57a..46bc6eb03 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
 import { observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { RectangleTemplate } from './FaceRectangles';
 
 @observer
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index 26e720c0d..ade4225d9 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -1,8 +1,8 @@
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Cast, NumCast } from '../../../fields/Types';
-import { observer } from 'mobx-react';
 import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast } from '../../../fields/Types';
 import FaceRectangle from './FaceRectangle';
 
 interface FaceRectanglesProps {
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f4c5167a5..008f10f26 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { DateField } from '../../../fields/DateField';
 import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index fd8d8ef56..7bc9d3f85 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,7 +1,6 @@
 import { Tooltip } from '@mui/material';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { ObservableGroupMap } from 'mobx-utils';
 import * as React from 'react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
 import { Doc, Field } from '../../../fields/Doc';
@@ -11,13 +10,13 @@ import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider } from '../StyleProvider';
 import { OpenWhere } from './DocumentView';
 import { FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 // Represents one row in a key value plane
 
@@ -35,12 +34,11 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     @observable private isPointerOver = false;
     @observable public isChecked = false;
     private checkbox = React.createRef<HTMLInputElement>();
-    constructor(props:any) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-
     @action
     handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.isChecked = e.currentTarget.checked;
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 32300d60a..8ad0b7dde 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { LinkManager } from '../../util/LinkManager';
 import './LinkDescriptionPopup.scss';
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index d0a9f10b4..ea23ecbea 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -2,13 +2,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import wiki from 'wikijs';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
-import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
@@ -16,10 +17,9 @@ import { LinkManager } from '../../util/LinkManager';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
-import * as React from 'react';
-import { ObservableReactComponent } from '../ObservableReactComponent';
 
 export class LinkInfo {
     private static _instance: Opt<LinkInfo>;
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 27d73a585..adccc9db6 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 42dfa59b7..35153f439 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -1,13 +1,10 @@
-import mapboxgl from 'mapbox-gl';
-import { MercatorCoordinate } from 'mapbox-gl';
-import { MapRef } from 'react-map-gl';
-import * as React from 'react';
-import * as d3 from 'd3';
 import * as turf from '@turf/turf';
 import { Position } from '@turf/turf';
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
-import { observer } from 'mobx-react';
-import { action, computed, observable, runInAction, makeObservable } from 'mobx';
+import * as d3 from 'd3';
+import { Feature, GeoJsonProperties, Geometry } from 'geojson';
+import mapboxgl, { MercatorCoordinate } from 'mapbox-gl';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { MapRef } from 'react-map-gl';
 
 export enum AnimationStatus {
     START = 'start',
diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
index f9607becf..7e99795b5 100644
--- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { IconButton } from 'browndash-components';
 import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { returnFalse, unimplementedFunction } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { IconButton } from 'browndash-components';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SettingsManager } from '../../../util/SettingsManager';
-import { IconLookup, faAdd, faCalendarDays, faRoute } from '@fortawesome/free-solid-svg-icons';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
 export class DirectionsAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 1b1b74e7c..08bea5d9d 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,26 +1,23 @@
+import { IconLookup, faAdd, faArrowDown, faArrowLeft, faArrowsRotate, faBicycle, faCalendarDays, faCar, faDiamondTurnRight, faEdit, faPersonWalking, faRoute } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import * as React from 'react';
+import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
+import { IconButton } from 'browndash-components';
+import { Position } from 'geojson';
 import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, NumListCast, Opt } from '../../../../fields/Doc';
+import * as React from 'react';
+import { CirclePicker, ColorResult } from 'react-color';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { CalendarManager } from '../../../util/CalendarManager';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
-import { Button, IconButton } from 'browndash-components';
 import { SettingsManager } from '../../../util/SettingsManager';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import './MapAnchorMenu.scss';
-import { NumCast, StrCast } from '../../../../fields/Types';
-import { IconLookup, faDiamondTurnRight, faCalendarDays, faEdit, faAdd, faRoute, faArrowLeft, faLocationDot, faArrowDown, faCar, faBicycle, faPersonWalking, faUpload, faArrowsRotate } from '@fortawesome/free-solid-svg-icons';
-import { DirectionsAnchorMenu } from './DirectionsAnchorMenu';
-import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material';
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
-import { MapBox } from './MapBox';
-import { List } from '../../../../fields/List';
 import { MarkerIcons } from './MarkerIcons';
-import { CirclePicker, ColorResult } from 'react-color';
-import { Position } from 'geojson';
-import { CalendarManager } from '../../../util/CalendarManager';
+// import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
 
 type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route';
 
@@ -52,7 +49,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public UpdateMarkerColor: (color: string) => void = unimplementedFunction;
     public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction;
 
-
     public Hide: () => void = unimplementedFunction;
 
     public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction;
@@ -74,22 +70,21 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     private title: string | undefined = undefined;
 
     public setPinDoc(pinDoc: Doc | undefined) {
-        if (pinDoc){
+        if (pinDoc) {
             this.pinDoc = pinDoc;
             this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`);
         }
-        
     }
 
     public setRouteDoc(routeDoc: Doc | undefined) {
-        if (routeDoc){
+        if (routeDoc) {
             this.routeDoc = routeDoc;
             this.title = StrCast(routeDoc.title ?? 'Map route');
         }
     }
 
     @action
-    public Reset(){
+    public Reset() {
         this.destinationSelected = false;
         this.currentRouteInfoMap = undefined;
         this.destinationFeatures = [];
@@ -296,34 +291,23 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return undefined;
     };
 
-    getDirectionsButton: JSX.Element = (
-        <IconButton 
-            tooltip="Get directions" 
-            onPointerDown={this.DirectionsClick}
-            icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} 
-            color={SettingsManager.userColor} />
-    )
+    getDirectionsButton: JSX.Element = (<IconButton tooltip="Get directions" onPointerDown={this.DirectionsClick} icon={<FontAwesomeIcon icon={faDiamondTurnRight as IconLookup} />} color={SettingsManager.userColor} />);
 
     getAddToCalendarButton = (docType: string): JSX.Element => {
         return (
-            <IconButton 
-            tooltip="Add to calendar" 
-            onPointerDown={() => {
-                CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc)
-            }} 
-            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
-            color={SettingsManager.userColor} 
+            <IconButton
+                tooltip="Add to calendar"
+                onPointerDown={() => {
+                    CalendarManager.Instance.open(undefined, docType === 'pin' ? this.pinDoc : this.routeDoc);
+                }}
+                icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />}
+                color={SettingsManager.userColor}
             />
-        )
-       
-    }
+        );
+    };
     addToCalendarButton: JSX.Element = (
-        <IconButton 
-            tooltip="Add to calendar" 
-            onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} 
-            icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} 
-            color={SettingsManager.userColor} />
-    )
+        <IconButton tooltip="Add to calendar" onPointerDown={() => CalendarManager.Instance.open(undefined, this.pinDoc)} icon={<FontAwesomeIcon icon={faCalendarDays as IconLookup} />} color={SettingsManager.userColor} />
+    );
 
     getLinkNoteToDocButton = (docType: string): JSX.Element => {
         return (
@@ -335,8 +319,8 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     color={SettingsManager.userColor}
                 />
             </div>
-        )
-    }
+        );
+    };
 
     linkNoteToPinOrRoutenButton: JSX.Element = (
         <div ref={this._commentRef}>
@@ -347,16 +331,9 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                 color={SettingsManager.userColor}
             />
         </div>
-    )
-
-    customizePinButton: JSX.Element = (
-        <IconButton 
-            tooltip="Customize pin" 
-            onPointerDown={this.CustomizeClick} 
-            icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} 
-            color={SettingsManager.userColor} 
-        />
-    )
+    );
+
+    customizePinButton: JSX.Element = (<IconButton tooltip="Customize pin" onPointerDown={this.CustomizeClick} icon={<FontAwesomeIcon icon={faEdit as IconLookup} />} color={SettingsManager.userColor} />);
 
     centerOnPinButton: JSX.Element = (
         <IconButton
@@ -365,7 +342,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon="compress-arrows-alt" />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     backButton: JSX.Element = (
         <IconButton
@@ -374,7 +351,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faArrowLeft as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     addRouteButton: JSX.Element = (
         <IconButton
@@ -383,7 +360,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faAdd as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     getDeleteButton = (type: string) => {
         return (
@@ -393,17 +370,10 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                 icon={<FontAwesomeIcon icon="trash-alt" />}
                 color={SettingsManager.userColor}
             />
-        )
-    }
+        );
+    };
 
-    animateRouteButton: JSX.Element = (
-        <IconButton 
-            tooltip="Animate route" 
-            onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} 
-            icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} 
-            color={SettingsManager.userColor} 
-        />
-    )
+    animateRouteButton: JSX.Element = (<IconButton tooltip="Animate route" onPointerDown={() => this.OpenAnimationPanel(this.routeDoc)} icon={<FontAwesomeIcon icon={faRoute as IconLookup} />} color={SettingsManager.userColor} />);
 
     revertToOriginalMarkerButton = (
         <IconButton
@@ -412,7 +382,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             icon={<FontAwesomeIcon icon={faArrowsRotate as IconLookup} />}
             color={SettingsManager.userColor}
         />
-    )
+    );
 
     render() {
         const buttons = (
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 7db139d74..ffd52fb0e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,8 +1,17 @@
+import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Checkbox, FormControlLabel, TextField } from '@mui/material';
+import * as turf from '@turf/turf';
 import { IconButton, Size, Type } from 'browndash-components';
+import * as d3 from 'd3';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
+import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
 import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { CirclePicker, ColorResult } from 'react-color';
+import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
+import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
@@ -22,22 +31,13 @@ import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
-import { MapAnchorMenu } from './MapAnchorMenu';
-import { ControlPosition, Layer, MapProvider, MapRef, Map as MapboxMap, Marker, MarkerProps, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
-import './MapBox.scss';
-// import { GeocoderControl } from './GeocoderControl';
-import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
-import { Checkbox, FormControlLabel, TextField } from '@mui/material';
-import * as turf from '@turf/turf';
-import * as d3 from 'd3';
-import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
-import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
-import { CirclePicker, ColorResult } from 'react-color';
-import { MarkerEvent } from 'react-map-gl/dist/esm/types';
 import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
 import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
+import { MapAnchorMenu } from './MapAnchorMenu';
+import './MapBox.scss';
 import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
 import { MarkerIcons } from './MarkerIcons';
+// import { GeocoderControl } from './GeocoderControl';
 
 // amongus
 /**
@@ -480,7 +480,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     // The pin that is selected
-    @observable selectedPinOrRoute: Doc | undefined;
+    @observable selectedPinOrRoute: Doc | undefined = undefined;
 
     @action
     deselectPinOrRoute = () => {
@@ -650,7 +650,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     deleteSelectedPinOrRoute = undoable(() => {
-        console.log('deleting')
+        console.log('deleting');
         if (this.selectedPinOrRoute) {
             // Removes filter
             Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
@@ -677,15 +677,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.temporaryRouteSource = {
                 type: 'FeatureCollection',
                 features: [],
-            }
-        })
-
+            };
+        });
 
         document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
     };
 
-   
-
     @action
     centerOnSelectedPin = () => {
         if (this.selectedPinOrRoute) {
@@ -882,8 +879,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
             this.temporaryRouteSource = {
                 type: 'FeatureCollection',
-                features: []
-            }
+                features: [],
+            };
             MapAnchorMenu.Instance.fadeOut(true);
             return mapRoute;
         }
@@ -994,8 +991,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-
-
     /**
      * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
      * and latitude). Sets the search results accordingly.
@@ -1388,7 +1383,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         }
     };
 
-
     getRouteAnimationOptions = (): JSX.Element => {
         return (
             <>
@@ -1423,20 +1417,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 <>
                     <div className="animation-suboptions">
                         <div>|</div>
-                        <FormControlLabel
-                            className="first-person-label"
-                            label="1st person animation:"
-                            labelPlacement="start"
-                            control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />}
-                        />
+                        <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this.isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
                         <div id="divider">|</div>
                         <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
                         <div id="divider">|</div>
-                        <div style={{display: 'flex', alignItems: 'center'}}>
+                        <div style={{ display: 'flex', alignItems: 'center' }}>
                             <div>Select Line Color: </div>
-                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color:any) => this.setAnimationLineColor(color)} />
+                            <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color: any) => this.setAnimationLineColor(color)} />
                         </div>
-                        
                     </div>
                 </>
             </>
@@ -1478,7 +1466,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const bearing = parseInt(e.target.value);
         if (!isNaN(bearing) && this._mapRef.current) {
-            console.log('bearing change')
+            console.log('bearing change');
             const fixedBearing = Math.max(0, Math.min(360, bearing));
             this._mapRef.current.setBearing(fixedBearing);
             this.dataDoc.map_bearing = fixedBearing;
@@ -1489,7 +1477,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const pitch = parseInt(e.target.value);
         if (!isNaN(pitch) && this._mapRef.current) {
-            console.log('pitch change')
+            console.log('pitch change');
             const fixedPitch = Math.max(0, Math.min(85, pitch));
             this._mapRef.current.setPitch(fixedPitch);
             this.dataDoc.map_pitch = fixedPitch;
@@ -1648,7 +1636,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         <MapboxMap
                             ref={this._mapRef}
                             mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
-                            viewState={(this.isAnimating || this.routeToAnimate) ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
+                            viewState={this.isAnimating || this.routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                             mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                             style={{
                                 position: 'absolute',
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index fdd8285d5..1dbbbb633 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -87,7 +87,7 @@
 //     private _dropDisposer?: DragManager.DragDropDisposer;
 //     private _disposers: { [name: string]: IReactionDisposer } = {};
 //     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
-//     @observable private _overlayAnnoInfo: Opt<Doc>;
+//     @observable private _overlayAnnoInfo: Opt<Doc> = undefined;
 //     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
 //     public static LayoutString(fieldKey: string) {
 //         return FieldView.LayoutString(MapBox2, fieldKey);
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 6a14427c0..70037f29c 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -1,33 +1,31 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import BingMapsReact from 'bingmaps-react';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { MapProvider, Map as MapboxMap } from 'react-map-gl';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
 import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
 import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { LinkManager } from '../../../util/LinkManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
-import { undoable, UndoManager } from '../../../util/UndoManager';
-import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
-import { Colors } from '../../global/globalEnums';
 import { SidebarAnnos } from '../../SidebarAnnos';
+import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
+import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
+import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
 import { FormattedTextBox } from '../formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../trails';
 import './MapBox.scss';
-import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
-import { MapProvider, Map as MapboxMap } from 'react-map-gl';
 
 // amongus
 /**
@@ -351,7 +349,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
     }, 'createpin');
 
     // The pin that is selected
-    @observable selectedPin: Doc | undefined;
+    @observable selectedPin: Doc | undefined = undefined;
 
     @action
     deselectPin = () => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 733febd2d..7f1d6b049 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -48,7 +48,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _sidebarRef = React.createRef<SidebarAnnos>();
 
     @observable private _searching: boolean = false;
-    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
+    @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy> = undefined;
     @observable private _pageControls = false;
 
     @computed get pdfUrl() {
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index e75b1ab6f..135db64e0 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -6,8 +6,9 @@ import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
 import ReplayIcon from '@mui/icons-material/Replay';
 import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material';
 import Typography from '@mui/material/Typography';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { IReactionDisposer, action, computed, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { NumListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
@@ -19,7 +20,6 @@ import questions from './PhysicsSimulationQuestions.json';
 import tutorials from './PhysicsSimulationTutorial.json';
 import Wall from './PhysicsSimulationWall';
 import Weight from './PhysicsSimulationWeight';
-import * as React from 'react';
 
 interface IWallProps {
     length: number;
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index f5077a07e..3b232ddd0 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -1,7 +1,7 @@
 import { computed, IReactionDisposer, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import './PhysicsSimulationBox.scss';
 import * as React from 'react';
+import './PhysicsSimulationBox.scss';
 
 interface IWallProps {
     length: number;
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 061a46f03..3b2fc033d 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import './RadialMenu.scss';
 import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
 
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
index c931202f1..10a90befd 100644
--- a/src/client/views/nodes/RadialMenuItem.tsx
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { UndoManager } from '../../util/UndoManager';
 
 export interface RadialMenuProps {
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index f01642236..658cfb1ca 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -8,15 +8,15 @@ import { List } from '../../../../fields/List';
 import { BoolCast, DocCast } from '../../../../fields/Types';
 import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
-import { Docs } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { Docs } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { Presentation } from '../../../util/TrackMovements';
 import { undoBatch } from '../../../util/UndoManager';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { media_state } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index d5d31b407..79ed69cdd 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import * as React from 'react';
 // import { Canvas } from '@react-three/fiber';
 import { computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 7e7eaee45..8e506ec64 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -2,25 +2,23 @@ let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnAlways, returnEmptyString } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { returnAlways, returnEmptyString, returnTrue } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
-import { InteractionUtils } from '../../util/InteractionUtils';
+import { ScriptManager } from '../../util/ScriptManager';
 import { CompileScript, ScriptParam } from '../../util/Scripting';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { ScriptManager } from '../../util/ScriptManager';
 import { ContextMenu } from '../ContextMenu';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { EditableView } from '../EditableView';
-import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { OverlayView } from '../OverlayView';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { DocumentIconContainer } from './DocumentIcon';
-import { DocFocusOptions, DocumentView } from './DocumentView';
 import './ScriptingBox.scss';
 const _global = (window /* browser */ || global) /* node */ as any;
 
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
index 9aab8c5a9..c9e15d314 100644
--- a/src/client/views/nodes/TaskCompletedBox.tsx
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
+import { Fade } from '@mui/material';
+import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import './TaskCompletedBox.scss';
-import { observable, action } from 'mobx';
-import { Fade } from '@mui/material';
 
 @observer
 export class TaskCompletionBox extends React.Component<{}> {
     @observable public static taskCompleted: boolean = false;
     @observable public static popupX: number = 500;
     @observable public static popupY: number = 150;
-    @observable public static textDisplayed: string;
+    @observable public static textDisplayed: string = '';
 
     @action
     public static toggleTaskCompleted = () => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f205dbd56..8e9cfe3d7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -74,8 +74,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._props.setContentView?.(this);
     }
 
-    @observable _stackedTimeline: any; // CollectionStackedTimeline ref
-    @observable static _nativeControls: boolean; // default html controls
+    @observable _stackedTimeline: any = undefined; // CollectionStackedTimeline ref
+    @observable static _nativeControls: boolean = false; // default html controls
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
     @observable _clicking = false; // used for transition between showing/hiding timeline
@@ -86,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @observable _finished: boolean = false; // has playback reached end of clip
     @observable _volume: number = 1;
     @observable _muted: boolean = false;
-    @observable _controlsTransform?: { X: number; Y: number };
+    @observable _controlsTransform?: { X: number; Y: number } = undefined;
     @observable _controlsVisible: boolean = true;
     @observable _scrubbing: boolean = false;
 
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index c034d95ea..3e46ff4e6 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -7,10 +7,10 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { Cast } from '../../../../fields/Types';
 import { numberRange } from '../../../../Utils';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { Colors } from './../../global/globalEnums';
 import './AudioWaveform.scss';
 import { WaveCanvas } from './WaveCanvas';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
 
 /**
  * AudioWaveform
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 989feb774..4fb1a7f17 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,40 +1,35 @@
-import * as React from 'react';
-import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from '../FieldView';
-import { StrCast } from '../../../../fields/Types';
+import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core';
+import dayGridPlugin from '@fullcalendar/daygrid';
+import multiMonthPlugin from '@fullcalendar/multimonth';
 import { makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
 import { dateRangeStrToDates } from '../../../../Utils';
-import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'
-import dayGridPlugin from '@fullcalendar/daygrid'
-import multiMonthPlugin from '@fullcalendar/multimonth'
-import { faListNumeric } from '@fortawesome/free-solid-svg-icons';
+import { Doc } from '../../../../fields/Doc';
+import { StrCast } from '../../../../fields/Types';
+import { ViewBoxBaseComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
 
 type CalendarView = 'month' | 'multi-month' | 'week';
 
 @observer
-export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
+export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string = 'calendar') {
         return FieldView.LayoutString(CalendarBox, fieldKey);
     }
 
-    componentDidMount(): void {
-        
-    }
+    componentDidMount(): void {}
 
-    componentWillUnmount(): void {
-        
-    }
+    componentWillUnmount(): void {}
 
-    _calendarRef = React.createRef<HTMLElement>()
+    _calendarRef = React.createRef<HTMLElement>();
 
-    get dateRangeStr (){
+    get dateRangeStr() {
         return StrCast(this.Document.date_range);
     }
 
-    // Choose a calendar view based on the date range 
-    get calendarViewType (): CalendarView {
+    // Choose a calendar view based on the date range
+    get calendarViewType(): CalendarView {
         const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
 
         if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
@@ -43,92 +38,83 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
         return 'week';
     }
 
-    get calendarStartDate () {
-        return this.dateRangeStr.split("|")[0];
+    get calendarStartDate() {
+        return this.dateRangeStr.split('|')[0];
     }
 
-    get calendarToDate () {
-        return this.dateRangeStr.split("|")[1];
+    get calendarToDate() {
+        return this.dateRangeStr.split('|')[1];
     }
 
-    get childDocs (): Doc[] {
+    get childDocs(): Doc[] {
         return this.childDocs; // get all sub docs for a calendar
     }
 
-    docBackgroundColor (type: string): string {
+    docBackgroundColor(type: string): string {
         // TODO: Return a different color based on the event type
         return 'blue';
     }
 
-    get calendarEvents (): EventSourceInput | undefined {
+    get calendarEvents(): EventSourceInput | undefined {
         if (this.childDocs.length === 0) return undefined;
         return this.childDocs.map((doc, idx) => {
             const docTitle = StrCast(doc.title);
             const docDateRange = StrCast(doc.date_range);
             const [startDate, endDate] = dateRangeStrToDates(docDateRange);
             const docType = doc.type;
-            const docDescription = doc.description ? StrCast(doc.description): "";
+            const docDescription = doc.description ? StrCast(doc.description) : '';
 
             return {
                 title: docTitle,
                 start: startDate,
                 end: endDate,
                 allDay: false,
-                classNames:[StrCast(docType)], // will determine the style 
+                classNames: [StrCast(docType)], // will determine the style
                 editable: false, // subject to change in the future
                 backgroundColor: this.docBackgroundColor(StrCast(doc.type)),
                 color: 'white',
                 extendedProps: {
-                    description: docDescription
+                    description: docDescription,
                 },
-                
-            }
-
-        })
+            };
+        });
     }
 
     handleEventClick = (arg: EventClickArg) => {
         // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
-    }
+    };
 
     calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!;
 
     // https://fullcalendar.io
     get calendar() {
-        return new Calendar(this.calendarEl, 
-            {
-                plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
-                headerToolbar: {
-                    left: 'prev,next today',
-                    center: 'title',
-                    right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
-                },
-                initialDate: this.calendarStartDate,
-                navLinks: true,
-                editable: false,
-                displayEventTime: false,
-                displayEventEnd: false,
-                events: this.calendarEvents,
-                eventClick: this.handleEventClick
-            }
-        )
-        
+        return new Calendar(this.calendarEl, {
+            plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
+            headerToolbar: {
+                left: 'prev,next today',
+                center: 'title',
+                right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
+            },
+            initialDate: this.calendarStartDate,
+            navLinks: true,
+            editable: false,
+            displayEventTime: false,
+            displayEventEnd: false,
+            events: this.calendarEvents,
+            eventClick: this.handleEventClick,
+        });
     }
 
-
-    constructor(props: any){
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-    render(){
+    render() {
         return (
-            <div className='calendar-box-conatiner'>
-                <div id='calendar-box-v1'>
-
-                </div>
-            </div>  
+            <div className="calendar-box-conatiner">
+                <div id="calendar-box-v1"></div>
+            </div>
         );
-        
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 6332b200d..1002ee403 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -1,17 +1,17 @@
 import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { NodeSelection } from 'prosemirror-state';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { Height, Width } from '../../../../fields/DocSymbols';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnTrue, Utils } from '../../../../Utils';
+import { emptyFunction, returnFalse, Utils } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { Transform } from '../../../util/Transform';
 import { DocFocusOptions, DocumentView } from '../DocumentView';
 import { FormattedTextBox } from './FormattedTextBox';
-import * as React from 'react';
 
 export class DashDocView {
     dom: HTMLSpanElement; // container for label and value
@@ -83,7 +83,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     _disposers: { [name: string]: IReactionDisposer } = {};
     _textBox: FormattedTextBox;
     @observable _dashDoc: Doc | undefined = undefined;
-    @observable _finalLayout: any;
+    @observable _finalLayout: any = undefined;
     @observable _width: number = 0;
     @observable _height: number = 0;
 
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 331ed1980..7e655531e 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -1,14 +1,13 @@
-import EquationEditor from './EquationEditor';
-import { IReactionDisposer, trace } from 'mobx';
+import { IReactionDisposer } from 'mobx';
 import { observer } from 'mobx-react';
 import { TextSelection } from 'prosemirror-state';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { StrCast } from '../../../../fields/Types';
 import './DashFieldView.scss';
+import EquationEditor from './EquationEditor';
 import { FormattedTextBox } from './FormattedTextBox';
-import * as React from 'react';
-import { AnyArray } from 'mongoose';
 
 export class EquationView {
     dom: HTMLDivElement; // container for label and value
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ad2fab8b0..8bf8abafa 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -11,6 +11,7 @@ import { keymap } from 'prosemirror-keymap';
 import { Fragment, Mark, Node, Slice } from 'prosemirror-model';
 import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
+import * as React from 'react';
 import { BsMarkdownFill } from 'react-icons/bs';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
@@ -70,9 +71,8 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-// import * as applyDevTools from 'prosemirror-dev-tools';
-import * as React from 'react';
 export const GoogleRef = 'googleDocId';
+// import * as applyDevTools from 'prosemirror-dev-tools';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 export interface FormattedTextBoxProps {}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 4881070fd..3b31f2d17 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _linkToRef = React.createRef<HTMLInputElement>();
 
     layoutDoc: Doc | undefined;
-    @observable public view?: EditorView;
+    @observable public view?: EditorView = undefined;
     public editorProps: FieldViewProps | undefined;
 
     public _brushMap: Map<string, Set<Mark>> = new Map();
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 9dc0c5180..b573f7c48 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,13 +1,11 @@
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { returnFalse } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../../Utils';
-import { Transform } from '../../../util/Transform';
 import { ViewBoxBaseComponent } from '../../DocComponent';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { DocumentView, DocumentViewInternal } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
-import * as React from 'react';
 
 @observer
 export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e213b2fc5..0305689e7 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation } from '../../../../fields/DocSymbols';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
@@ -24,7 +24,7 @@ import { SerializationHelper } from '../../../util/SerializationHelper';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
 import { CollectionView } from '../../collections/CollectionView';
 import { TreeView } from '../../collections/TreeView';
@@ -83,12 +83,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves
     _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed.  Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
     _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
+    _presTimer: NodeJS.Timeout | undefined;
 
     @observable public static Instance: PresBox;
 
     @observable _isChildActive = false;
     @observable _moveOnFromAudio: boolean = true;
-    @observable _presTimer!: NodeJS.Timeout;
 
     @observable _eleArray: HTMLElement[] = [];
     @observable _dragArray: HTMLElement[] = [];
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index cf19ff6bc..5ea9e9979 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,18 +1,17 @@
-import * as React from 'react';
 import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
 import { LinkFollower } from '../../util/LinkFollower';
+import { LinkManager } from '../../util/LinkManager';
 import { undoBatch } from '../../util/UndoManager';
 import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
-import { LinkManager } from '../../util/LinkManager';
-import { Rect } from 'react-measure';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index db21d9a3d..da8a88803 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,20 +1,20 @@
-import * as React from 'react';
-import './GPTPopup.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Type } from 'browndash-components';
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { CgClose } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
+import { Utils } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
-import { DocUtils, Docs } from '../../../documents/Documents';
-import { Button, IconButton, Type } from 'browndash-components';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { CgClose } from 'react-icons/cg';
-import { AnchorMenu } from '../AnchorMenu';
-import { gptImageCall } from '../../../apis/gpt/GPT';
 import { Networking } from '../../../Network';
-import { Utils } from '../../../../Utils';
+import { gptImageCall } from '../../../apis/gpt/GPT';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { AnchorMenu } from '../AnchorMenu';
+import './GPTPopup.scss';
 
 export enum GPTPopupMode {
     SUMMARY,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index e342c25b3..b6d027d30 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -302,7 +302,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
         }
     };
 
-    @observable private _scrollTimer: any;
+    @observable private _scrollTimer: any = undefined;
 
     onScroll = (e: React.UIEvent<HTMLElement>) => {
         if (this._mainCont.current && !this._forcedScroll) {
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index ccfccb771..14187833f 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -2,24 +2,23 @@ import { Tooltip } from '@mui/material';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
+import { Doc, DocListCastAsync, Field } from '../../../fields/Doc';
 import { DirectLinks } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, StrCast } from '../../../fields/Types';
-import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { LinkManager } from '../../util/LinkManager';
+import { SearchUtil } from '../../util/SearchUtil';
+import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { ViewBoxBaseComponent } from '../DocComponent';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { IRecommendation, Recommendation } from '../newlightbox/components';
+import { fetchRecommendations } from '../newlightbox/utils';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
-import { fetchRecommendations } from '../newlightbox/utils';
-import { IRecommendation, Recommendation } from '../newlightbox/components';
-import { Colors } from '../global/globalEnums';
-import { SettingsManager } from '../../util/SettingsManager';
-import { SearchUtil } from '../../util/SearchUtil';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index 39e778b76..daacb368b 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,14 +1,14 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ListBox } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DocFocusOptions } from '../nodes/DocumentView';
-import { emptyFunction } from '../../../Utils';
 import { SettingsManager } from '../../util/SettingsManager';
+import { DocFocusOptions } from '../nodes/DocumentView';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 0f3487cd1..575d5849e 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -16,15 +16,15 @@ import { ReportManager } from '../../util/reportManager/ReportManager';
 import { ServerStats } from '../../util/ServerStats';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { CollectionLinearView } from '../collections/collectionLinear';
 import { DashboardView } from '../DashboardView';
 import { Colors } from '../global/globalEnums';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentViewInternal } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
 import './TopBar.scss';
-import { SnappingManager } from '../../util/SnappingManager';
 
 /**
  * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index f1739a41a..94458563e 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -3,16 +3,15 @@ import { faPhoneSlash, faSync } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
+import { SnappingManager } from '../../util/SnappingManager';
 import '../../views/nodes/WebBox.scss';
 import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './DashWebRTCVideo.scss';
 import { hangup, initialize, refreshVideos } from './WebCamLogic';
-import * as React from 'react';
-import { SnappingManager } from '../../util/SnappingManager';
 
 /**
  * This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index b8081648f..a9f7c085f 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -1,13 +1,13 @@
+import { computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
-import { observer } from 'mobx-react';
-import { observable, computed } from 'mobx';
+import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/util/CurrentUserUtils';
 import { CompileScript } from '../client/util/Scripting';
-import { makeInterface } from '../fields/Schema';
 import { ObjectField } from '../fields/ObjectField';
 import { RefField } from '../fields/RefField';
-import { DocServer } from '../client/DocServer';
-import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { makeInterface } from '../fields/Schema';
 
 @observer
 class Repl extends React.Component {
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 02038c426..f46adef77 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -1,19 +1,19 @@
-import { action, configure, observable, ObservableMap, Lambda } from 'mobx';
+import { action, configure, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
-import { observer } from 'mobx-react';
-import { Doc, Field, FieldResult, Opt } from '../fields/Doc';
 import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { CompileScript } from '../client/util/Scripting';
+import { EditableView } from '../client/views/EditableView';
+import CursorField from '../fields/CursorField';
+import { DateField } from '../fields/DateField';
+import { Doc, Field, FieldResult } from '../fields/Doc';
 import { Id } from '../fields/FieldSymbols';
 import { List } from '../fields/List';
-import { URLField } from '../fields/URLField';
-import { EditableView } from '../client/views/EditableView';
-import { CompileScript } from '../client/util/Scripting';
 import { RichTextField } from '../fields/RichTextField';
-import { DateField } from '../fields/DateField';
 import { ScriptField } from '../fields/ScriptField';
-import CursorField from '../fields/CursorField';
-import { resolvedPorts } from '../client/util/CurrentUserUtils';
+import { URLField } from '../fields/URLField';
 
 DateField;
 URLField;
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index 46f5a8e1c..84917ae53 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,8 +1,7 @@
-import { ObjectField } from './ObjectField';
-import { observable } from 'mobx';
+import { createSimpleSchema, object, serializable } from 'serializr';
 import { Deserializable } from '../client/util/SerializationHelper';
-import { serializable, createSimpleSchema, object, date } from 'serializr';
-import { FieldChanged, ToScriptString, ToString, Copy } from './FieldSymbols';
+import { Copy, FieldChanged, ToScriptString, ToString } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
 
 export type CursorPosition = {
     x: number;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8903a9f97..9dc3c173d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -265,8 +265,8 @@ export class Doc extends RefField {
     @observable public [DocAcl]: { [key: string]: symbol } = {};
     @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
     @observable public [DirectLinks] = new ObservableSet<Doc>();
-    @observable public [AudioPlay]: any; // meant to store sound object from Howl
-    @observable public [Animation]: Opt<Doc>;
+    @observable public [AudioPlay]: any = undefined; // meant to store sound object from Howl
+    @observable public [Animation]: Opt<Doc> = undefined;
     @observable public [Highlight]: boolean = false;
     @observable public [Brushed]: boolean = false;
     @observable public [DocViews] = new ObservableSet<DocumentView>();
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 8c8ff1ea3..b8ad552d2 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -2,7 +2,7 @@ import { action, computed, makeObservable, observable } from 'mobx';
 import { alias, list, serializable } from 'serializr';
 import { DocServer } from '../client/DocServer';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper';
 import { Field } from './Doc';
 import { FieldTuples, Self, SelfProxy } from './DocSymbols';
 import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols';
@@ -11,7 +11,7 @@ import { ProxyField } from './Proxy';
 import { RefField } from './RefField';
 import { listSpec } from './Schema';
 import { Cast } from './Types';
-import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
+import { containedFieldChangedHandler, deleteProperty, getter, setter } from './util';
 
 function toObjectField(field: Field) {
     return field instanceof RefField ? new ProxyField(field) : field;
@@ -102,8 +102,8 @@ class ListImpl<T extends Field> extends ObjectField {
                 items.length === 0 && deleteCount
                     ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
                     : items.length && !deleteCount && start === list.__fieldTuples.length
-                    ? { op: '$addToSet', items, length: list.__fieldTuples.length }
-                    : undefined
+                      ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+                      : undefined
             );
             return res.map(toRealField);
         }),
diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts
index c076f5fe1..3a46e3581 100644
--- a/src/fields/Proxy.ts
+++ b/src/fields/Proxy.ts
@@ -1,12 +1,12 @@
-import { Deserializable } from '../client/util/SerializationHelper';
-import { Field, FieldWaiting, Opt } from './Doc';
+import { action, computed, observable, runInAction } from 'mobx';
 import { primitive, serializable } from 'serializr';
-import { observable, action, runInAction, computed } from 'mobx';
 import { DocServer } from '../client/DocServer';
-import { RefField } from './RefField';
-import { ObjectField } from './ObjectField';
-import { Id, Copy, ToScriptString, ToString, ToValue } from './FieldSymbols';
 import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { Field, FieldWaiting, Opt } from './Doc';
+import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols';
+import { ObjectField } from './ObjectField';
+import { RefField } from './RefField';
 
 function deserializeProxy(field: any) {
     if (!field.cache.field) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 545fe4478..b73520999 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,11 +1,11 @@
 import { $mobx, action, observable, runInAction, trace } from 'mobx';
 import { computedFn } from 'mobx-utils';
+import { returnZero } from '../Utils';
 import { DocServer } from '../client/DocServer';
 import { LinkManager } from '../client/util/LinkManager';
 import { SerializationHelper } from '../client/util/SerializationHelper';
 import { UndoManager } from '../client/util/UndoManager';
-import { returnZero } from '../Utils';
-import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
+import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
 import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols';
 import { List } from './List';
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index d2598c2db..e333e6a2e 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -1,19 +1,19 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as rp from 'request-promise';
+import { Utils } from '../Utils';
 import { DocServer } from '../client/DocServer';
-import { Docs } from '../client/documents/Documents';
 import { Networking } from '../client/Network';
+import { Docs } from '../client/documents/Documents';
 import { MainViewModal } from '../client/views/MainViewModal';
 import { Doc, Opt } from '../fields/Doc';
 import { List } from '../fields/List';
 import { listSpec } from '../fields/Schema';
 import { Cast } from '../fields/Types';
-import { Utils } from '../Utils';
 import './ImageUpload.scss';
 import { MobileInterface } from './MobileInterface';
-import * as React from 'react';
 const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../client/views/global/globalCssVariables.module.scss'); // prettier-ignore
 export interface ImageUploadProps {
     Document: Doc; // Target document for upload (upload location)
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 2e595e4bc..23e19585a 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { DocServer } from '../client/DocServer';
 import { DragManager } from '../client/util/DragManager';
 import { Doc } from '../fields/Doc';
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index bdd657575..e3d8f9394 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,52 +1,33 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import {
-    faTasks,
-    faReply,
-    faQuoteLeft,
-    faHandPointLeft,
-    faFolderOpen,
+    faAddressCard,
+    faAlignLeft,
+    faAlignRight,
     faAngleDoubleLeft,
-    faExternalLinkSquareAlt,
-    faMobile,
-    faThLarge,
-    faWindowClose,
-    faEdit,
-    faTrashAlt,
-    faPalette,
     faAngleRight,
+    faArrowDown,
+    faArrowLeft,
+    faArrowRight,
+    faArrowUp,
+    faArrowsAltH,
+    faAsterisk,
+    faBars,
     faBell,
-    faTrash,
+    faBolt,
+    faBook,
+    faBrain,
+    faBullseye,
+    faCalculator,
     faCamera,
-    faExpand,
     faCaretDown,
     faCaretLeft,
     faCaretRight,
     faCaretSquareDown,
     faCaretSquareRight,
-    faArrowsAltH,
-    faPlus,
-    faMinus,
-    faTerminal,
-    faToggleOn,
-    faFile as fileSolid,
-    faExternalLinkAlt,
-    faLocationArrow,
-    faSearch,
-    faFileDownload,
-    faStop,
-    faCalculator,
-    faWindowMaximize,
-    faAddressCard,
-    faQuestionCircle,
-    faArrowLeft,
-    faArrowRight,
-    faArrowDown,
-    faArrowUp,
-    faBolt,
-    faBullseye,
     faCaretUp,
     faCat,
     faCheck,
+    faChevronLeft,
     faChevronRight,
     faClipboard,
     faClone,
@@ -54,76 +35,95 @@ import {
     faCommentAlt,
     faCompressArrowsAlt,
     faCut,
+    faEdit,
     faEllipsisV,
     faEraser,
     faExclamation,
+    faExpand,
+    faExternalLinkAlt,
+    faExternalLinkSquareAlt,
+    faEye,
     faFileAlt,
     faFileAudio,
+    faFileDownload,
     faFilePdf,
     faFilm,
     faFilter,
+    faFolderOpen,
     faFont,
     faGlobeAsia,
+    faHandPointLeft,
     faHighlighter,
+    faHome,
+    faImage,
+    faLocationArrow,
+    faLongArrowAltLeft,
     faLongArrowAltRight,
     faMicrophone,
+    faMinus,
+    faMobile,
     faMousePointer,
     faMusic,
     faObjectGroup,
+    faPaintBrush,
+    faPalette,
     faPause,
     faPen,
     faPenNib,
     faPhone,
     faPlay,
+    faPlus,
     faPortrait,
+    faQuestionCircle,
+    faQuoteLeft,
     faRedoAlt,
+    faReply,
+    faSearch,
     faStamp,
     faStickyNote,
+    faStop,
+    faTasks,
+    faTerminal,
+    faTh,
+    faThLarge,
     faThumbtack,
+    faTimes,
+    faToggleOn,
+    faTrash,
+    faTrashAlt,
     faTree,
     faTv,
-    faBook,
     faUndoAlt,
     faVideo,
-    faAsterisk,
-    faBrain,
-    faImage,
-    faPaintBrush,
-    faTimes,
-    faEye,
-    faHome,
-    faLongArrowAltLeft,
-    faBars,
-    faTh,
-    faChevronLeft,
-    faAlignRight,
-    faAlignLeft,
+    faWindowClose,
+    faWindowMaximize,
+    faFile as fileSolid,
 } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../Utils';
 import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
+import { Docs, DocumentOptions } from '../client/documents/Documents';
 import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
 import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
-import { SettingsManager, ColorScheme } from '../client/util/SettingsManager';
+import { SettingsManager } from '../client/util/SettingsManager';
 import { Transform } from '../client/util/Transform';
 import { UndoManager } from '../client/util/UndoManager';
-import { TabDocView } from '../client/views/collections/TabDocView';
 import { GestureOverlay } from '../client/views/GestureOverlay';
+import { TabDocView } from '../client/views/collections/TabDocView';
 import { AudioBox } from '../client/views/nodes/AudioBox';
 import { DocumentView } from '../client/views/nodes/DocumentView';
-import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
 import { RadialMenu } from '../client/views/nodes/RadialMenu';
+import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu';
 import { Doc, DocListCast } from '../fields/Doc';
 import { InkTool } from '../fields/InkField';
 import { List } from '../fields/List';
 import { ScriptField } from '../fields/ScriptField';
 import { Cast, FieldValue, StrCast } from '../fields/Types';
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils';
-import { Uploader } from './ImageUpload';
 import './AudioUpload.scss';
+import { Uploader } from './ImageUpload';
 import './ImageUpload.scss';
 import './MobileInterface.scss';
 
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 8f21966ae..d46977816 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -14,9 +14,7 @@ declare module 'pdfjs-dist/web/pdf_viewer';
 declare module 'react-jsx-parser';
 
 declare module 'express-flash';
-declare module 'connect-flash' {
-    interface flash {}
-}
+declare module 'connect-flash';
 declare module 'connect-mongo';
 declare module '@mui/material';
 
diff --git a/test/test.ts b/test/test.ts
index 489aa3025..ee14a9340 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,37 +1,41 @@
 import { expect } from 'chai';
 import 'mocha';
 const { JSDOM } = require('jsdom');
-const dom = new JSDOM("", {
-    url: `http://localhost:${resolvedPorts.server}`
+const dom = new JSDOM('', {
+    url: `http://localhost:${resolvedPorts.server}`,
 });
 (global as any).window = dom.window;
 
-
-import { autorun, reaction } from "mobx";
+import { reaction } from 'mobx';
+import { resolvedPorts } from '../src/client/util/CurrentUserUtils';
 import { Doc } from '../src/fields/Doc';
+import { createSchema, defaultSpec, makeInterface } from '../src/fields/Schema';
 import { Cast } from '../src/fields/Types';
-import { createSchema, makeInterface, defaultSpec } from '../src/fields/Schema';
 import { ImageField } from '../src/fields/URLField';
-import { resolvedPorts } from '../src/client/util/CurrentUserUtils';
-describe("Document", () => {
+describe('Document', () => {
     it('should hold fields', () => {
-        const key = "Test";
-        const key2 = "Test2";
+        const key = 'Test';
+        const key2 = 'Test2';
         const field = 15;
         const doc = new Doc();
         doc[key] = field;
-        const getField = Cast(doc[key], "number");
-        const getField2 = Cast(doc[key2], "number");
+        const getField = Cast(doc[key], 'number');
+        const getField2 = Cast(doc[key2], 'number');
         expect(getField).to.equal(field);
         expect(getField2).to.equal(undefined);
     });
 
     it('should update', () => {
         const doc = new Doc();
-        const key = "Test";
-        const key2 = "Test2";
+        const key = 'Test';
+        const key2 = 'Test2';
         let ran = false;
-        reaction(() => doc[key], (field) => { ran = true; });
+        reaction(
+            () => doc[key],
+            field => {
+                ran = true;
+            }
+        );
         expect(ran).to.equal(false);
 
         doc[key2] = 4;
@@ -44,55 +48,55 @@ describe("Document", () => {
 });
 
 const testSchema1 = createSchema({
-    a: "number",
-    b: "string",
-    c: "boolean",
+    a: 'number',
+    b: 'string',
+    c: 'boolean',
     d: ImageField,
-    e: Doc
+    e: Doc,
 });
 
 type TestDoc = makeInterface<[typeof testSchema1]>;
 const TestDoc = makeInterface(testSchema1);
 
 const testSchema2 = createSchema({
-    a: defaultSpec("boolean", true),
-    b: defaultSpec("number", 5),
-    c: defaultSpec("string", "hello world")
+    a: defaultSpec('boolean', true),
+    b: defaultSpec('number', 5),
+    c: defaultSpec('string', 'hello world'),
 });
 
 type TestDoc2 = makeInterface<[typeof testSchema2]>;
 const TestDoc2 = makeInterface(testSchema2);
 
 const testSchema3 = createSchema({
-    a: TestDoc2
+    a: TestDoc2,
 });
 
 type TestDoc3 = makeInterface<[typeof testSchema3]>;
 const TestDoc3 = makeInterface(testSchema3);
 
-describe("Schema", () => {
-    it("should do the right thing 1", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
+describe('Schema', () => {
+    it('should do the right thing 1', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
         test1.a = 5;
-        test1.b = "hello";
+        test1.b = 'hello';
         test1.c = true;
         test1.d = ifield;
         test1.e = test2;
         const doc = TestDoc(test1);
         expect(doc.a).to.equal(5);
-        expect(doc.b).to.equal("hello");
+        expect(doc.b).to.equal('hello');
         expect(doc.c).to.equal(true);
         expect(doc.d).to.equal(ifield);
         expect(doc.e).to.equal(test2);
     });
 
-    it("should do the right thing 2", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
-        test1.a = "hello";
+    it('should do the right thing 2', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
+        test1.a = 'hello';
         test1.b = 5;
         test1.c = test2;
         test1.d = true;
@@ -105,11 +109,11 @@ describe("Schema", () => {
         expect(doc.e).to.equal(undefined);
     });
 
-    it("should do the right thing 3", () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
-        const ifield = new ImageField(new URL("http://google.com"));
-        test1.a = "hello";
+    it('should do the right thing 3', () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
+        const ifield = new ImageField(new URL('http://google.com'));
+        test1.a = 'hello';
         test1.b = 5;
         test1.c = test2;
         test1.d = true;
@@ -122,40 +126,40 @@ describe("Schema", () => {
         expect(doc.e).to.equal(undefined);
     });
 
-    it("should do the right thing 4", () => {
+    it('should do the right thing 4', () => {
         const doc = TestDoc2();
         expect(doc.a).to.equal(true);
         expect(doc.b).to.equal(5);
-        expect(doc.c).to.equal("hello world");
+        expect(doc.c).to.equal('hello world');
 
-        const d2 = new Doc;
+        const d2 = new Doc();
         d2.a = false;
         d2.b = 4;
-        d2.c = "goodbye";
+        d2.c = 'goodbye';
         const doc2 = TestDoc2(d2);
         expect(doc2.a).to.equal(false);
         expect(doc2.b).to.equal(4);
-        expect(doc2.c).to.equal("goodbye");
+        expect(doc2.c).to.equal('goodbye');
 
-        const d3 = new Doc;
-        d3.a = "hello";
+        const d3 = new Doc();
+        d3.a = 'hello';
         d3.b = false;
         d3.c = 5;
         const doc3 = TestDoc2(d3);
         expect(doc3.a).to.equal(true);
         expect(doc3.b).to.equal(5);
-        expect(doc3.c).to.equal("hello world");
+        expect(doc3.c).to.equal('hello world');
     });
 
-    it("should do the right thing 5", async () => {
-        const test1 = new Doc;
-        const test2 = new Doc;
+    it('should do the right thing 5', async () => {
+        const test1 = new Doc();
+        const test2 = new Doc();
         const doc = TestDoc3(test1);
         expect(doc.a).to.equal(undefined);
         test1.a = test2;
         const doc2 = (await doc.a)!;
         expect(doc2.a).to.equal(true);
         expect(doc2.b).to.equal(5);
-        expect(doc2.c).to.equal("hello world");
+        expect(doc2.c).to.equal('hello world');
     });
 });
-- 
cgit v1.2.3-70-g09d2


From fdc0bf7c54af252178f587709630d36726484b91 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 2 Jan 2024 13:26:53 -0500
Subject: fixing more .props => ._props refernces.

---
 src/client/util/BranchingTrailManager.tsx          |  2 +-
 src/client/util/CaptureManager.tsx                 |  2 +-
 src/client/util/DictationManager.ts                |  8 +--
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/GroupMemberView.tsx                | 29 +++++----
 src/client/util/SelectionManager.ts                | 10 +--
 src/client/util/SharingManager.tsx                 |  2 +-
 src/client/util/TrackMovements.ts                  |  4 +-
 src/client/views/DocComponent.tsx                  |  3 +-
 src/client/views/DocumentButtonBar.tsx             | 74 +++-------------------
 src/client/views/DocumentDecorations.tsx           | 10 +--
 src/client/views/FilterPanel.tsx                   | 18 ++++--
 src/client/views/InkStrokeProperties.ts            |  2 +-
 src/client/views/PropertiesButtons.tsx             |  6 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/TemplateMenu.tsx                  |  6 +-
 .../views/collections/CollectionCarousel3DView.tsx |  4 +-
 .../views/collections/CollectionCarouselView.tsx   |  2 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionMenu.tsx    |  4 +-
 .../views/collections/CollectionNoteTakingView.tsx |  2 +-
 .../views/collections/CollectionPileView.tsx       |  4 +-
 .../collections/CollectionStackedTimeline.tsx      |  2 +-
 src/client/views/collections/TreeView.tsx          |  6 +-
 .../CollectionFreeFormLinkView.tsx                 |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collectionGrid/CollectionGridView.tsx          | 72 ++++++++++-----------
 .../collectionMulticolumn/MulticolumnResizer.tsx   | 11 +---
 .../collectionMulticolumn/MultirowResizer.tsx      | 11 +---
 .../nodes/DataVizBox/components/LineChart.tsx      |  6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 16 ++---
 src/client/views/nodes/MapBox/MapBox2.tsx          | 34 +++++-----
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |  4 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   | 54 ++++++++--------
 .../nodes/PhysicsBox/PhysicsSimulationBox.tsx      | 34 +++++-----
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  6 +-
 src/client/views/nodes/ScreenshotBox.tsx           | 50 +++++++--------
 src/client/views/nodes/ScriptingBox.tsx            | 14 ++--
 src/client/views/nodes/audio/AudioWaveform.tsx     |  2 +-
 .../views/nodes/formattedText/DashDocView.tsx      | 16 ++---
 .../views/nodes/formattedText/EquationView.tsx     |  4 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     | 16 ++---
 .../views/nodes/importBox/ImportElementBox.tsx     |  4 +-
 src/client/views/pdf/Annotation.tsx                | 55 ++++++++--------
 src/client/views/search/SearchBox.tsx              | 10 +--
 src/client/views/selectedDoc/SelectedDocView.tsx   |  6 +-
 47 files changed, 289 insertions(+), 350 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 11f16493f..02879e3c4 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -54,7 +54,7 @@ export class BranchingTrailManager extends React.Component {
     @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
 
     observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
-        const presId = pres.props.Document[Id];
+        const presId = pres.Document[Id];
         if (this.prevPresId === presId) {
             return;
         }
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index c1e0a5b2e..2e13aff2f 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -84,7 +84,7 @@ export class CaptureManager extends React.Component<{}> {
                         onClick={() => {
                             const selected = SelectionManager.Views.slice();
                             SelectionManager.DeselectAll();
-                            selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
+                            selected.map(dv => dv.props.removeDocument?.(dv.Document));
                             this.close();
                         }}>
                         Cancel
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 039bb360e..82c63695c 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -290,7 +290,7 @@ export namespace DictationManager {
             if (!ctor) {
                 return false;
             }
-            return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
+            return Cast(Doc.GetProto(view.Document).data, ctor) !== undefined;
         };
 
         const validate = (target: DocumentView, types: DocumentType[]) => {
@@ -318,7 +318,7 @@ export namespace DictationManager {
             [
                 'clear',
                 {
-                    action: (target: DocumentView) => (Doc.GetProto(target.props.Document).data = new List()),
+                    action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
                     restrictTo: [DocumentType.COL],
                 },
             ],
@@ -347,7 +347,7 @@ export namespace DictationManager {
                 action: (target: DocumentView, matches: RegExpExecArray) => {
                     const count = interpretNumber(matches[1]);
                     const what = matches[2];
-                    const dataDoc = Doc.GetProto(target.props.Document);
+                    const dataDoc = Doc.GetProto(target.Document);
                     const fieldKey = 'data';
                     if (isNaN(count)) {
                         return;
@@ -372,7 +372,7 @@ export namespace DictationManager {
                 expression: /view as (freeform|stacking|masonry|schema|tree)/g,
                 action: (target: DocumentView, matches: RegExpExecArray) => {
                     const mode = matches[1];
-                    mode && (target.props.Document._type_collection = mode);
+                    mode && (target.Document._type_collection = mode);
                 },
                 restrictTo: [DocumentType.COL],
             }
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index fe3a52be7..9ede18ed5 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -142,7 +142,7 @@ export namespace DragManager {
             this.linkSourceGetAnchor = linkSourceGetAnchor;
         }
         get dragDocument() {
-            return this.linkDragView.props.Document;
+            return this.linkDragView.Document;
         }
         linkSourceGetAnchor: () => Doc;
         linkSourceDoc?: Doc;
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 7de0f336f..894583711 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -19,35 +19,38 @@ interface GroupMemberViewProps {
 @observer
 export class GroupMemberView extends React.Component<GroupMemberViewProps> {
     @observable private memberSort: 'ascending' | 'descending' | 'none' = 'none';
+    get group() {
+        return this.props.group;
+    }
 
     private get editingInterface() {
-        let members: string[] = this.props.group ? JSON.parse(StrCast(this.props.group.members)) : [];
+        let members: string[] = this.group ? JSON.parse(StrCast(this.group.members)) : [];
         members = this.memberSort === 'ascending' ? members.sort() : this.memberSort === 'descending' ? members.sort().reverse() : members;
 
-        const options: UserOptions[] = this.props.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.props.group.members)) as string[]).includes(option.value)) : [];
+        const options: UserOptions[] = this.group ? GroupManager.Instance.options.filter(option => !(JSON.parse(StrCast(this.group.members)) as string[]).includes(option.value)) : [];
 
-        const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group);
+        const hasEditAccess = GroupManager.Instance.hasEditAccess(this.group);
 
-        return !this.props.group ? null : (
+        return !this.group ? null : (
             <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
                 <div className="editing-header">
                     <input
                         className="group-title"
                         style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
-                        value={StrCast(this.props.group.title || this.props.group.groupName)}
-                        onChange={e => (this.props.group.title = e.currentTarget.value)}
+                        value={StrCast(this.group.title || this.group.groupName)}
+                        onChange={e => (this.group.title = e.currentTarget.value)}
                         disabled={!hasEditAccess}></input>
                     <div className={'memberView-closeButton'}>
                         <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
                     </div>
-                    {GroupManager.Instance.hasEditAccess(this.props.group) ? (
+                    {GroupManager.Instance.hasEditAccess(this.group) ? (
                         <div className="group-buttons">
                             <div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
                                 <Select
                                     className="add-member-dropdown"
                                     isSearchable={true}
                                     options={options}
-                                    onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.props.group, (selectedOption as UserOptions).value)}
+                                    onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
                                     placeholder={'Add members'}
                                     value={null}
                                     styles={{
@@ -72,8 +75,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                                     }}
                                 />
                             </div>
-                            <div className={'delete-button'}>
-                                <Button text={'Delete Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.props.group)} />
+                            <div className="delete-button">
+                                <Button text="Delete Group" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.deleteGroup(this.group)} />
                             </div>
                         </div>
                     ) : null}
@@ -81,14 +84,14 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
                         Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
                     </div>
                 </div>
-                <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+                <div className="style-divider" style={{ background: StrCast(Doc.UserDoc().userColor) }} />
                 <div className="editing-contents" style={{ height: hasEditAccess ? '62%' : '85%' }}>
                     {members.map(member => (
                         <div className="editing-row" key={member}>
                             <div className="user-email">{member}</div>
                             {hasEditAccess ? (
-                                <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}>
-                                    <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)} />
+                                <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+                                    <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
                                 </div>
                             ) : null}
                         </div>
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 07bbde36c..53e4a2853 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -33,17 +33,17 @@ export class SelectionManager {
 
     public static SelectView = action((docView: DocumentView | undefined, extendSelection: boolean): void => {
         if (!docView) this.DeselectAll();
-        else if (!docView.SELECTED) {
+        else if (!docView.IsSelected) {
             if (!extendSelection) this.DeselectAll();
             this.Instance.SelectedViews.push(docView);
-            docView.SELECTED = true;
+            docView.IsSelected = true;
             docView._props.whenChildContentsActiveChanged(true);
         }
     });
 
     public static DeselectView = action((docView?: DocumentView): void => {
         if (docView && this.Instance.SelectedViews.includes(docView)) {
-            docView.SELECTED = false;
+            docView.IsSelected = false;
             this.Instance.SelectedViews.splice(this.Instance.SelectedViews.indexOf(docView), 1);
             docView._props.whenChildContentsActiveChanged(false);
         }
@@ -55,14 +55,14 @@ export class SelectionManager {
         LinkManager.currentLinkAnchor = undefined;
         runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
         this.Instance.SelectedViews.forEach(dv => {
-            dv.SELECTED = false;
+            dv.IsSelected = false;
             dv._props.whenChildContentsActiveChanged(false);
         });
         this.Instance.SelectedViews.length = 0;
         if (found) this.SelectView(found, false);
     };
 
-    public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.SELECTED);
+    public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
     public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
     public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
     public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index ac7de9872..fddf735e3 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -94,7 +94,7 @@ export class SharingManager extends React.Component<{}> {
         this.populateUsers();
         runInAction(() => {
             this.targetDocView = target;
-            this.targetDoc = target_doc || target?.props.Document;
+            this.targetDoc = target_doc || target?.Document;
             DictationOverlay.Instance.hasActiveModal = true;
             this.isOpen = this.targetDoc !== undefined;
             this.permissions = SharingPermissions.Augment;
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index 0b197cf9a..f9c2d522f 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -126,12 +126,12 @@ export class TrackMovements {
         }
 
         // init the dispose funcs on the page
-        const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data);
+        const docList = DocListCast(CollectionDockingView.Instance?.Document.data);
         this.updateRecordingFFViewsFromTabs(docList);
 
         // create a reaction to monitor changes in tabs
         this.tabChangeDisposeFunc = reaction(
-            () => CollectionDockingView.Instance?.props.Document.data,
+            () => CollectionDockingView.Instance?.Document.data,
             change => {
                 // TODO: consider changing between dashboards
                 // console.info('change in tabs', change);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 235b0dc68..b5bd17572 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -55,7 +55,6 @@ interface ViewBoxBaseProps {
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
-        //@computed get Document(): T { return schemaCtor(this.props.Document); }
         get Document() {
             return this._props.Document;
         }
@@ -127,7 +126,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             const indocs = doc instanceof Doc ? [doc] : doc;
             const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
 
-            // docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+            // docs.forEach(doc => doc.annotationOn === this.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
             const targetDataDoc = this.dataDoc;
             const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
             const toRemove = value.filter(v => docs.includes(v));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 3938b81cd..63d2a5e08 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -25,27 +25,10 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView';
 import { PinProps } from './nodes/trails';
 import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
 
-const cloud: IconProp = 'cloud-upload-alt';
-const fetch: IconProp = 'sync-alt';
-
-enum UtilityButtonState {
-    Default,
-    OpenRight,
-    OpenExternally,
-}
-
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
     private _dragRef = React.createRef<HTMLDivElement>();
-    private _pullAnimating = false;
-    private _pushAnimating = false;
-    private _pullColorAnimating = false;
-    @observable public isAnimatingFetch = false;
-    @observable public isAnimatingPulse = false;
-
     @observable public static Instance: DocumentButtonBar;
-    public static hasPushedHack = false;
-    public static hasPulledHack = false;
 
     constructor(props: any) {
         super(props);
@@ -53,51 +36,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         DocumentButtonBar.Instance = this;
     }
 
-    public startPullOutcome = action((success: boolean) => {
-        if (!this._pullAnimating) {
-            this._pullAnimating = true;
-            this.pullIcon = success ? 'check-circle' : 'stop-circle';
-            setTimeout(
-                () =>
-                    runInAction(() => {
-                        this.pullIcon = 'arrow-alt-circle-down';
-                        this._pullAnimating = false;
-                    }),
-                1000
-            );
-        }
-    });
-
-    public startPushOutcome = action((success: boolean) => {
-        this.isAnimatingPulse = false;
-        if (!this._pushAnimating) {
-            this._pushAnimating = true;
-            this.pushIcon = success ? 'check-circle' : 'stop-circle';
-            setTimeout(
-                () =>
-                    runInAction(() => {
-                        this.pushIcon = 'arrow-alt-circle-up';
-                        this._pushAnimating = false;
-                    }),
-                1000
-            );
-        }
-    });
-
-    public setPullState = action((unchanged: boolean) => {
-        this.isAnimatingFetch = false;
-        if (!this._pullColorAnimating) {
-            this._pullColorAnimating = true;
-            this.pullColor = unchanged ? 'lawngreen' : 'red';
-            setTimeout(this.clearPullColor, 1000);
-        }
-    });
-
-    private clearPullColor = action(() => {
-        this.pullColor = 'white';
-        this._pullColorAnimating = false;
-    });
-
     get view0() {
         return this._props.views()?.[0];
     }
@@ -367,7 +305,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         if (this._dragRef.current) {
             const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
-            const [left, top] = dragDocView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            const [left, top] = dragDocView.screenToLocalTransform().inverse().transformPoint(0, 0);
             dragData.defaultDropAction = 'embed';
             dragData.canEmbed = true;
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
@@ -385,7 +323,8 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         return !view0 ? null : (
             <Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
                 <div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
-                    {/* <Flyout
+                    {
+                        /* <Flyout
                         anchorPoint={anchorPoints.LEFT_TOP}
                         onOpen={action(() => (this._embedDown = true))}
                         onClose={action(() => (this._embedDown = false))}
@@ -400,7 +339,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                         <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
                             <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />
                         </div>
-                    </Flyout> */}
+                    </Flyout> */
+
+                        <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
+                            <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />
+                        </div>
+                    }
                 </div>
             </Tooltip>
         );
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dab6e6193..51fd33222 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -208,7 +208,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             SelectionManager.Views.map(dv => dv.Document),
             dragDocView._props.dropAction
         );
-        dragData.offset = dragDocView._props.ScreenToLocalTransform().transformDirection(e.x - left, e.y - top);
+        dragData.offset = dragDocView.screenToLocalTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView._props.moveDocument;
         dragData.removeDocument = dragDocView._props.removeDocument;
         dragData.isDocDecorationMove = true;
@@ -353,7 +353,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
-        const newloccentern = seldocview._props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+        const newloccentern = seldocview.screenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
         seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -462,7 +462,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
             return [a[0] + atob[0] * t, a[1] + atob[1] * t];
         };
-        const tl = docView._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const tl = docView.screenToLocalTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -530,7 +530,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            const refCent = docView._props.ScreenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const refCent = docView.screenToLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
@@ -632,7 +632,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @computed get rotCenter() {
         const lastView = SelectionManager.Views.lastElement();
         if (lastView) {
-            const invXf = lastView._props.ScreenToLocalTransform().inverse();
+            const invXf = lastView.screenToLocalTransform().inverse();
             const seldoc = lastView.layoutDoc;
             const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index d3075a5cd..c4f65a5ca 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableMap } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
@@ -18,22 +18,28 @@ import { undoable } from '../util/UndoManager';
 import './FilterPanel.scss';
 import { FieldView } from './nodes/FieldView';
 import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface filterProps {
     Document: Doc;
 }
 
 @observer
-export class FilterPanel extends React.Component<filterProps> {
+export class FilterPanel extends ObservableReactComponent<filterProps> {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FilterPanel, fieldKey);
     }
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     /**
      * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
      */
-    @computed get targetDoc() {
-        return this.props.Document;
+    get targetDoc() {
+        return this._props.Document;
     }
     @computed get targetDocChildKey() {
         const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc);
@@ -112,7 +118,7 @@ export class FilterPanel extends React.Component<filterProps> {
     // }
 
     gatherFieldValues(childDocs: Doc[], facetKey: string) {
-        const valueSet = new Set<string>(StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
+        const valueSet = new Set<string>(StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
         let rtFields = 0;
         let subDocs = childDocs;
         if (subDocs.length > 0) {
@@ -223,7 +229,7 @@ export class FilterPanel extends React.Component<filterProps> {
     facetValues = (facetHeader: string) => {
         const allCollectionDocs = new Set<Doc>();
         SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
-        const set = new Set<string>([...StrListCast(this.props.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
+        const set = new Set<string>([...StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
         if (facetHeader === 'tags')
             allCollectionDocs.forEach(child =>
                 StrListCast(child[facetHeader])
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index a628c7120..f31ea2994 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -244,7 +244,7 @@ export class InkStrokeProperties {
         this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
             const order = controlIndex % 4;
             const closed = InkingStroke.IsClosed(ink);
-            const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec('number'), []);
+            const brokenIndices = Cast(inkView.Document.brokenInkIndices, listSpec('number'), []);
             if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) {
                 const cpt_before = ink[controlIndex];
                 const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY };
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index f1dadcd5e..5905f8dbc 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         SelectionManager.Views.filter(dv => dv.docView)
             .map(dv => dv.docView!)
             .forEach(docView => {
-                const linkButton = IsFollowLinkScript(docView.props.Document.onClick);
+                const linkButton = IsFollowLinkScript(docView.Document.onClick);
                 docView.noOnClick();
                 switch (onClick) {
                     case 'enterPortal':
@@ -422,11 +422,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         break;
                     case 'linkInPlace':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
+                        docView.Document.followLinkLocation = linkButton ? OpenWhere.lightbox : undefined;
                         break;
                     case 'linkOnRight':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
+                        docView.Document.followLinkLocation = linkButton ? OpenWhere.addRight : undefined;
                         break;
                 }
             });
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index c8f6d6013..835e61d55 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -121,7 +121,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.Highlighting:
             if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
             if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
-                const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.SELECTED).length;
+                const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
                 const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 791928b4a..b42f3c1e9 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -79,14 +79,14 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     componentDidMount() {
         !this._addedKeys && (this._addedKeys = new ObservableSet());
-        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))), ...Array.from(Object.keys(this.props.docViews[0].props.Document))]
+        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].Document))), ...Array.from(Object.keys(this.props.docViews[0].Document))]
             .filter(key => key.startsWith('layout_'))
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
 
     return100 = () => 300;
     @computed get scriptField() {
-        const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.props.Document) as any });
+        const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.Document) as any });
         return script ? () => script : undefined;
     }
     templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
@@ -95,7 +95,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     render() {
         TraceMobx();
-        const firstDoc = this.props.docViews[0].props.Document;
+        const firstDoc = this.props.docViews[0].Document;
         const templateName = StrCast(firstDoc.layout_fieldKey, 'layout').replace('layout_', '');
         const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data);
         const addedTypes = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data);
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index a11c53d4d..bf308904e 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -143,10 +143,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         const whichButton = this.layoutDoc.showScrollButton;
         return (
             <>
-                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
+                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
                     {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
                 </div>
-                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
+                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
                     {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
                 </div>
             </>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 5d09f14ef..b6acf3153 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -120,7 +120,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
                 }}>
                 {this.content}
-                {this.props.Document._chromeHidden ? null : this.buttons}
+                {this.Document._chromeHidden ? null : this.buttons}
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 868df7a3b..c46f54c70 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -493,7 +493,7 @@ export class CollectionDockingView extends CollectionSubView() {
             Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
         }
         if (CollectionDockingView.Instance) {
-            const dview = CollectionDockingView.Instance.props.Document;
+            const dview = CollectionDockingView.Instance.Document;
             const fieldKey = CollectionDockingView.Instance.props.fieldKey;
             Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc);
             this.tabMap.delete(tab);
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 2a6cb081f..98ae01591 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -446,7 +446,7 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
     @observable private suggestions: string[] = [];
 
     get document() {
-        return this.props.docView.props.Document;
+        return this.props.docView.Document;
     }
 
     @computed private get descending() {
@@ -572,7 +572,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
     @observable private resize = false;
     private resizeListenerDisposer: Opt<Lambda>;
     get document() {
-        return this.props.docView.props.Document;
+        return this.props.docView.Document;
     }
 
     @computed get panelWidth() {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index f9d258490..0744814dd 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -527,7 +527,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 addDocument={this.addDocument}
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
-                Document={this.props.Document}
+                Document={this.Document}
                 TemplateDataDocument={this.props.TemplateDataDocument}
                 resizeColumns={this.resizeColumns}
                 renderChildren={this.children}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e5068645f..359fdf543 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -94,7 +94,7 @@ export class CollectionPileView extends CollectionSubView() {
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
             this.layoutDoc._freeform_panX = 0;
             this.layoutDoc._freeform_panY = -10;
-            this.props.Document._freeform_pileEngine = computePassLayout.name;
+            this.Document._freeform_pileEngine = computePassLayout.name;
         } else {
             const defaultSize = NumCast(this.Document._starburstDiameter, 400);
             this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
@@ -104,7 +104,7 @@ export class CollectionPileView extends CollectionSubView() {
             this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
             this.layoutDoc.background;
-            this.props.Document._freeform_pileEngine = computeStarburstLayout.name;
+            this.Document._freeform_pileEngine = computeStarburstLayout.name;
         }
     });
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index fa3844aa4..a63688354 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -843,7 +843,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
         return (
             <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
                 {inner.view}
-                {!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
+                {!inner.anchor.view || !inner.anchor.view.IsSelected ? null : (
                     <>
                         <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} />
                         <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 72882ac17..f2ceaa681 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -169,7 +169,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this.childDocList(this.fieldKey + '_annotations');
     }
     @computed get selected() {
-        return this._docRef?.SELECTED;
+        return this._docRef?.IsSelected;
     }
 
     childDocList(field: string) {
@@ -204,10 +204,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         this._disposers.selection?.();
         if (!docView) {
             this._editTitle = false;
-        } else if (docView.SELECTED) {
+        } else if (docView.IsSelected) {
             this._editTitle = true;
             this._disposers.selection = reaction(
-                () => docView.SELECTED,
+                () => docView.IsSelected,
                 isSel => !isSel && this.setEditTitle(undefined)
             );
         } else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6337c8d34..6131fe037 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -229,8 +229,8 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
         const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
         const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
         const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
-        const aActive = A.SELECTED || A.Document[Brushed];
-        const bActive = B.SELECTED || B.Document[Brushed];
+        const aActive = A.IsSelected || A.Document[Brushed];
+        const bActive = B.IsSelected || B.Document[Brushed];
 
         const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
         const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cc0833698..44654c8fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -912,7 +912,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 for (var j = 0; j < otherCtrlPts.length - 3; j += 4) {
                     const neighboringSegment = i === j || i === j - 4 || i === j + 4;
                     // Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
-                    if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
+                    if (ink?.Document === otherInk.Document && neighboringSegment) continue;
 
                     const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
                     const c0 = otherCurve.get(0);
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 3e75257e5..0f750c4f8 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -33,35 +33,35 @@ export class CollectionGridView extends CollectionSubView() {
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
 
     @computed get numCols() {
-        return NumCast(this.props.Document.gridNumCols, 10);
+        return NumCast(this.Document.gridNumCols, 10);
     }
     @computed get rowHeight() {
-        return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight;
+        return this._rowHeight === undefined ? NumCast(this.Document.gridRowHeight, 100) : this._rowHeight;
     }
     // sets the default width and height of the grid nodes
     @computed get defaultW() {
-        return NumCast(this.props.Document.gridDefaultW, 2);
+        return NumCast(this.Document.gridDefaultW, 2);
     }
     @computed get defaultH() {
-        return NumCast(this.props.Document.gridDefaultH, 2);
+        return NumCast(this.Document.gridDefaultH, 2);
     }
 
     @computed get colWidthPlusGap() {
-        return (this.props.PanelWidth() - this.margin) / this.numCols;
+        return (this._props.PanelWidth() - this.margin) / this.numCols;
     }
     @computed get rowHeightPlusGap() {
         return this.rowHeight + this.margin;
     }
 
     @computed get margin() {
-        return NumCast(this.props.Document.margin, 10);
+        return NumCast(this.Document.margin, 10);
     } // sets the margin between grid nodes
 
     @computed get flexGrid() {
-        return BoolCast(this.props.Document.gridFlex, true);
+        return BoolCast(this.Document.gridFlex, true);
     } // is grid static/flexible i.e. whether nodes be moved around and resized
     @computed get compaction() {
-        return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, 'vertical'));
+        return StrCast(this.Document.gridStartCompaction, StrCast(this.Document.gridCompaction, 'vertical'));
     } // is grid static/flexible i.e. whether nodes be moved around and resized
 
     /**
@@ -94,12 +94,12 @@ export class CollectionGridView extends CollectionSubView() {
 
         // updates the layouts if the reset button has been clicked
         this._resetListenerDisposer = reaction(
-            () => this.props.Document.gridResetLayout,
+            () => this.Document.gridResetLayout,
             reset => {
                 if (reset && this.flexGrid) {
                     this.setLayout(this.childLayoutPairs.map((pair, index) => this.makeLayoutItem(pair.layout, this.unflexedPosition(index))));
                 }
-                this.props.Document.gridResetLayout = false;
+                this.Document.gridResetLayout = false;
             }
         );
     }
@@ -130,7 +130,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Maps the x- and y- coordinates of the event to a grid cell.
      */
     screenToCell(sx: number, sy: number) {
-        const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
+        const pt = this._props.ScreenToLocalTransform().transformPoint(sx, sy);
         const x = Math.floor(pt[0] / this.colWidthPlusGap);
         const y = Math.floor((pt[1] + this._scroll) / this.rowHeight);
         return { x, y };
@@ -159,25 +159,25 @@ export class CollectionGridView extends CollectionSubView() {
         const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
         const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
 
-        return this.props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
+        return this._props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
     };
 
     /**
      * @returns the layout list converted from JSON
      */
     get savedLayoutList() {
-        return (this.props.Document.gridLayoutString ? JSON.parse(StrCast(this.props.Document.gridLayoutString)) : []) as Layout[];
+        return (this.Document.gridLayoutString ? JSON.parse(StrCast(this.Document.gridLayoutString)) : []) as Layout[];
     }
 
     /**
      * Stores the layout list on the Document as JSON
      */
     setLayoutList(layouts: Layout[]) {
-        this.props.Document.gridLayoutString = JSON.stringify(layouts);
+        this.Document.gridLayoutString = JSON.stringify(layouts);
     }
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive();
-    isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive();
+    isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
     /**
      *
      * @param layout
@@ -199,9 +199,9 @@ export class CollectionGridView extends CollectionSubView() {
                 PanelWidth={width}
                 PanelHeight={height}
                 ScreenToLocalTransform={dxf}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 onClick={this.onChildClickHandler}
-                renderDepth={this.props.renderDepth + 1}
+                renderDepth={this._props.renderDepth + 1}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
@@ -222,12 +222,12 @@ export class CollectionGridView extends CollectionSubView() {
                 if (gridLayout) Object.assign(gridLayout, layoutArray.find(layout => layout.i === doc[Id]) || gridLayout);
             });
 
-            if (this.props.Document.gridStartCompaction) {
+            if (this.Document.gridStartCompaction) {
                 undoBatch(() => {
-                    this.props.Document.gridCompaction = this.props.Document.gridStartCompaction;
+                    this.Document.gridCompaction = this.Document.gridStartCompaction;
                     this.setLayoutList(savedLayouts);
                 })();
-                this.props.Document.gridStartCompaction = undefined;
+                this.Document.gridStartCompaction = undefined;
             } else {
                 undoBatch(() => this.setLayoutList(savedLayouts))();
             }
@@ -249,7 +249,7 @@ export class CollectionGridView extends CollectionSubView() {
                 const height = () => (this.flexGrid ? l.h : this.defaultH) * this.rowHeightPlusGap - this.margin;
                 child &&
                     collector.push(
-                        <div key={child.layout[Id]} className={'document-wrapper' + (this.flexGrid && this.props.isSelected() ? '' : ' static')}>
+                        <div key={child.layout[Id]} className={'document-wrapper' + (this.flexGrid && this._props.isSelected() ? '' : ' static')}>
                             {this.getDisplayDoc(child.layout, dxf, width, height)}
                         </div>
                     );
@@ -317,7 +317,7 @@ export class CollectionGridView extends CollectionSubView() {
             e,
             returnFalse,
             action(() => {
-                undoBatch(() => (this.props.Document.gridRowHeight = this._rowHeight))();
+                undoBatch(() => (this.Document.gridRowHeight = this._rowHeight))();
                 this._rowHeight = undefined;
             }),
             emptyFunction,
@@ -331,8 +331,8 @@ export class CollectionGridView extends CollectionSubView() {
      */
     onContextMenu = () => {
         const displayOptionsMenu: ContextMenuProps[] = [];
-        displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.props.Document.display = this.props.Document.display ? undefined : 'contents'), icon: 'copy' });
-        displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.props.Document.centerY = !this.props.Document.centerY), icon: 'copy' });
+        displayOptionsMenu.push({ description: 'Toggle Content Display Style', event: () => (this.Document.display = this.Document.display ? undefined : 'contents'), icon: 'copy' });
+        displayOptionsMenu.push({ description: 'Toggle Vertical Centering', event: () => (this.Document.centerY = !this.Document.centerY), icon: 'copy' });
         ContextMenu.Instance.addItem({ description: 'Display', subitems: displayOptionsMenu, icon: 'tv' });
     };
 
@@ -340,7 +340,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Handles text document creation on double click.
      */
     onPointerDown = (e: React.PointerEvent) => {
-        if (this.props.isContentActive(true)) {
+        if (this._props.isContentActive(true)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -352,7 +352,7 @@ export class CollectionGridView extends CollectionSubView() {
                             action(() => {
                                 const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
                                 FormattedTextBox.SetSelectOnLoad(text); // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-                                Doc.AddDocToList(this.props.Document, this.props.fieldKey, text);
+                                Doc.AddDocToList(this.Document, this._props.fieldKey, text);
                                 this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
                             })
                         )();
@@ -360,7 +360,7 @@ export class CollectionGridView extends CollectionSubView() {
                 },
                 false
             );
-            if (this.props.isSelected()) e.stopPropagation();
+            if (this._props.isSelected()) e.stopPropagation();
         }
     };
 
@@ -369,7 +369,7 @@ export class CollectionGridView extends CollectionSubView() {
             <div
                 className="collectionGridView-contents"
                 ref={this.createDashEventsTarget}
-                style={{ pointerEvents: !this.props.isContentActive() ? 'none' : undefined }}
+                style={{ pointerEvents: !this._props.isContentActive() ? 'none' : undefined }}
                 onContextMenu={this.onContextMenu}
                 onPointerDown={this.onPointerDown}
                 onDrop={this.onExternalDrop}>
@@ -379,29 +379,29 @@ export class CollectionGridView extends CollectionSubView() {
                     style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, 'white') }}
                     onWheel={e => e.stopPropagation()}
                     onScroll={action(e => {
-                        if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
+                        if (!this._props.isSelected()) e.currentTarget.scrollTop = this._scroll;
                         else this._scroll = e.currentTarget.scrollTop;
                     })}>
                     <Grid
-                        width={this.props.PanelWidth()}
+                        width={this._props.PanelWidth()}
                         nodeList={this.contents.length ? this.contents : null}
                         layout={this.contents.length ? this.renderedLayoutList : undefined}
-                        childrenDraggable={this.props.isSelected() ? true : false}
+                        childrenDraggable={this._props.isSelected() ? true : false}
                         numCols={this.numCols}
                         rowHeight={this.rowHeight}
                         setLayout={this.setLayout}
-                        transformScale={this.props.ScreenToLocalTransform().Scale}
+                        transformScale={this._props.ScreenToLocalTransform().Scale}
                         compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
-                        preventCollision={BoolCast(this.props.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
+                        preventCollision={BoolCast(this.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
                         margin={this.margin}
                     />
                     <input
                         className="rowHeightSlider"
                         type="range"
-                        style={{ width: this.props.PanelHeight() - 30 }}
+                        style={{ width: this._props.PanelHeight() - 30 }}
                         min={1}
                         value={this.rowHeight}
-                        max={this.props.PanelHeight() - 30}
+                        max={this._props.PanelHeight() - 30}
                         onPointerDown={this.onSliderDown}
                         onChange={this.onSliderChange}
                     />
diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
index ea99bff2e..c38c6dc4e 100644
--- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx
@@ -18,12 +18,8 @@ interface ResizerProps {
     select: (isCtrlPressed: boolean) => void;
 }
 
-const resizerOpacity = 1;
-
 @observer
 export default class ResizeBar extends React.Component<ResizerProps> {
-    @observable private isHoverActive = false;
-    @observable private isResizingActive = false;
     private _resizeUndo?: UndoManager.Batch;
 
     @action
@@ -35,7 +31,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         window.removeEventListener('pointerup', this.onPointerUp);
         window.addEventListener('pointermove', this.onPointerMove);
         window.addEventListener('pointerup', this.onPointerUp);
-        this.isResizingActive = true;
         this._resizeUndo = UndoManager.StartBatch('multcol resizing');
     };
 
@@ -80,8 +75,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
 
     @action
     private onPointerUp = () => {
-        this.isResizingActive = false;
-        this.isHoverActive = false;
         window.removeEventListener('pointermove', this.onPointerMove);
         window.removeEventListener('pointerup', this.onPointerUp);
         this._resizeUndo?.end();
@@ -96,9 +89,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     pointerEvents: this.props.isContentActive?.() ? 'all' : 'none',
                     width: this.props.width,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
-                }}
-                onPointerEnter={action(() => (this.isHoverActive = true))}
-                onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+                }}>
                 <div className={'multiColumnResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
index 7dee65e58..6f1b3b425 100644
--- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
+++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx
@@ -17,12 +17,8 @@ interface ResizerProps {
     toBottom?: Doc;
 }
 
-const resizerOpacity = 1;
-
 @observer
 export default class ResizeBar extends React.Component<ResizerProps> {
-    @observable private isHoverActive = false;
-    @observable private isResizingActive = false;
     private _resizeUndo?: UndoManager.Batch;
 
     @action
@@ -33,7 +29,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
         window.removeEventListener('pointerup', this.onPointerUp);
         window.addEventListener('pointermove', this.onPointerMove);
         window.addEventListener('pointerup', this.onPointerUp);
-        this.isResizingActive = true;
         this._resizeUndo = UndoManager.StartBatch('multcol resizing');
     };
 
@@ -78,8 +73,6 @@ export default class ResizeBar extends React.Component<ResizerProps> {
 
     @action
     private onPointerUp = () => {
-        this.isResizingActive = false;
-        this.isHoverActive = false;
         window.removeEventListener('pointermove', this.onPointerMove);
         window.removeEventListener('pointerup', this.onPointerUp);
         this._resizeUndo?.end();
@@ -94,9 +87,7 @@ export default class ResizeBar extends React.Component<ResizerProps> {
                     pointerEvents: this.props.isContentActive?.() ? 'all' : 'none',
                     height: this.props.height,
                     backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor),
-                }}
-                onPointerEnter={action(() => (this.isHoverActive = true))}
-                onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))}>
+                }}>
                 <div className={'multiRowResizer-hdl'} onPointerDown={e => this.registerResizing(e)} />
             </div>
         );
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index a69f083d1..50a8bf83d 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -358,7 +358,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
         const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
         if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
             return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
-                <div className="chart-container" style={{ width: this.props.width + this.props.margin.right }}>
+                <div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
                     <div className="graph-title">
                         <EditableText
                             val={StrCast(this._props.layoutDoc[titleAccessor])}
@@ -378,8 +378,8 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
                             <Button
                                 onClick={e => {
                                     console.log('test plzz');
-                                    this.props.vizBox.sidebarBtnDown;
-                                    this.props.vizBox.sidebarAddDocument;
+                                    this._props.vizBox.sidebarBtnDown;
+                                    this._props.vizBox.sidebarAddDocument;
                                 }}></Button>
                         </div>
                     ) : null}
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index f127fecf3..5365fe1b2 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -169,7 +169,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
             return (
                 <div
                     className="tableBox"
-                    style={{ width: this.props.width + this.props.margin.right }}
+                    style={{ width: this._props.width + this._props.margin.right }}
                     tabIndex={0}
                     onKeyDown={e => {
                         if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f8f4b94a2..d07824099 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -881,7 +881,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
-    screenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+    contentScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -925,7 +925,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     PanelHeight={this.panelHeight}
                     setHeight={this.setHeight}
                     isContentActive={this.isContentActive}
-                    ScreenToLocalTransform={this.screenToLocal}
+                    ScreenToLocalTransform={this.contentScreenToLocal}
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
                     setTitleFocus={this.setTitleFocus}
@@ -1329,10 +1329,10 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     }
 
     @observable _selected = false;
-    public get SELECTED() {
+    public get IsSelected() {
         return this._selected;
     }
-    public set SELECTED(val) {
+    public set IsSelected(val) {
         runInAction(() => (this._selected = val));
     }
     @observable public static LongPress = false;
@@ -1431,9 +1431,9 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
     @computed get hideLinkButton() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
+        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.IsSelected ? ':selected' : ''));
     }
-    hideLinkCount = () => this._props.renderDepth === -1 || (this.SELECTED && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
+    hideLinkCount = () => this._props.renderDepth === -1 || (this.IsSelected && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
     @computed get linkCountView() {
         return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
@@ -1568,9 +1568,9 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
     scaleToScreenSpace = () => (1 / (this._props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
-    isSelected = () => this.SELECTED;
+    isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
-        if (this.SELECTED && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
+        if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
         else {
             SelectionManager.SelectView(this, extendSelection);
             if (focusSelection) {
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 1dbbbb633..722a347f1 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -127,7 +127,7 @@
 //     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
 
 //     componentDidMount() {
-//         this.props.setContentView?.(this);
+//         this._props.setContentView?.(this);
 //     }
 
 //     @action
@@ -358,9 +358,9 @@
 //             e,
 //             (e, down, delta) =>
 //                 runInAction(() => {
-//                     const localDelta = this.props
+//                     const localDelta = this._props
 //                         .ScreenToLocalTransform()
-//                         .scale(this.props.NativeDimScaling?.() || 1)
+//                         .scale(this._props.NativeDimScaling?.() || 1)
 //                         .transformDirection(delta[0], delta[1]);
 //                     const fullWidth = NumCast(this.layoutDoc._width);
 //                     const mapWidth = fullWidth - this.sidebarWidth();
@@ -380,12 +380,12 @@
 //         );
 //     };
 
-//     sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+//     sidebarWidth = () => (Number(this.layout_sidebarWidthPercent.substring(0, this.layout_sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
 //     @computed get layout_sidebarWidthPercent() {
 //         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
 //     }
 //     @computed get sidebarColor() {
-//         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+//         return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
 //     }
 
 //     /**
@@ -444,7 +444,7 @@
 //                 key="sidebar"
 //                 title="Toggle Sidebar"
 //                 style={{
-//                     display: !this.props.isContentActive() ? 'none' : undefined,
+//                     display: !this._props.isContentActive() ? 'none' : undefined,
 //                     top: StrCast(this.layoutDoc._layout_showTitle) === 'title' ? 20 : 5,
 //                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
 //                 }}
@@ -481,7 +481,7 @@
 //     };
 
 //     pointerEvents = () => {
-//         return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.IsDragging ? undefined : 'none';
+//         return this._props.isContentActive() === false ? 'none' : this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.IsDragging ? undefined : 'none';
 //     };
 //     @computed get annotationLayer() {
 //         return (
@@ -489,7 +489,7 @@
 //                 {this.inlineTextAnnotations
 //                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
 //                     .map(anno => (
-//                         <Annotation key={`${anno[Id]}-annotation`} {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
+//                         <Annotation key={`${anno[Id]}-annotation`} {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} />
 //                     ))}
 //             </div>
 //         );
@@ -515,13 +515,13 @@
 //         // }
 //     };
 
-//     panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
-//     panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-//     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-//     transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-//     opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
-//     infoWidth = () => this.props.PanelWidth() / 5;
-//     infoHeight = () => this.props.PanelHeight() / 5;
+//     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+//     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+//     scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+//     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+//     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+//     infoWidth = () => this._props.PanelWidth() / 5;
+//     infoHeight = () => this._props.PanelHeight() / 5;
 //     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
 //     savedAnnotations = () => this._savedAnnotations;
 
@@ -556,7 +556,7 @@
 //                                 .map(marker => (
 //                                     <MapBoxInfoWindow
 //                                         key={marker[Id]}
-//                                         {...this.props}
+//                                         {...this._props}
 //                                         setContentView={emptyFunction}
 //                                         place={marker}
 //                                         markerMap={this.markerMap}
@@ -575,7 +575,7 @@
 //                 <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
 //                     <SidebarAnnos
 //                         ref={this._sidebarRef}
-//                         {...this.props}
+//                         {...this._props}
 //                         fieldKey={this.fieldKey}
 //                         Document={this.Document}
 //                         layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 8760c8600..34e237007 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -18,10 +18,10 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     get mapBoxView() {
-        return this.props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
+        return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
     }
     get mapBox() {
-        return this.props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+        return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
     }
 
     render() {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 70037f29c..2c31bbab7 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -89,7 +89,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
     }
     @computed get sidebarColor() {
-        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4'));
+        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
     }
     @computed get SidebarKey() {
         return this.fieldKey + '_sidebar';
@@ -97,7 +97,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     componentDidMount() {
         this._unmounting = false;
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
     }
 
     _unmounting = false;
@@ -162,9 +162,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             e,
             (e, down, delta) =>
                 runInAction(() => {
-                    const localDelta = this.props
+                    const localDelta = this._props
                         .ScreenToLocalTransform()
-                        .scale(this.props.NativeDimScaling?.() || 1)
+                        .scale(this._props.NativeDimScaling?.() || 1)
                         .transformDirection(delta[0], delta[1]);
                     const fullWidth = NumCast(this.layoutDoc._width);
                     const mapWidth = fullWidth - this.sidebarWidth();
@@ -183,7 +183,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
         );
     };
-    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth();
+    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();
 
     /**
      * Handles toggle of sidebar on click the little comment button
@@ -195,7 +195,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 key="sidebar"
                 title="Toggle Sidebar"
                 style={{
-                    display: !this.props.isContentActive() ? 'none' : undefined,
+                    display: !this._props.isContentActive() ? 'none' : undefined,
                     top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
                     backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                 }}
@@ -232,12 +232,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
             FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
-        const docView = this.props.DocumentView?.();
+        const docView = this._props.DocumentView?.();
         docView &&
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                 dragComplete: e => {
                     if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
-                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
+                        e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
@@ -276,15 +276,15 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey);
 
-    pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
+    pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
 
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
-    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter];
-    infoWidth = () => this.props.PanelWidth() / 5;
-    infoHeight = () => this.props.PanelHeight() / 5;
+    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
+    infoWidth = () => this._props.PanelWidth() / 5;
+    infoHeight = () => this._props.PanelHeight() / 5;
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     savedAnnotations = () => this._savedAnnotations;
 
@@ -399,9 +399,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
 
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
-        const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-        const y = point.y + this.props.PanelHeight() / 2 + 32;
-        const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+        const y = point.y + this._props.PanelHeight() / 2 + 32;
+        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -412,7 +412,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
      */
     @action
     mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
-        this.props.select(false);
+        this._props.select(false);
         this.deselectPin();
     };
     /*
@@ -677,9 +677,9 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 let target = document.elementFromPoint(e.x, e.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
-                        const x = cpt[0] - (this.props.PanelWidth() - this.sidebarWidth()) / 2;
-                        const y = cpt[1] - 32 /* height of search bar */ - this.props.PanelHeight() / 2;
+                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
+                        const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
                         this.createPushpin(location.latitude, location.longitude);
                         break;
@@ -776,8 +776,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                                   .map((pushpin, i) => (
                                       <DocumentView
                                           key={i}
-                                          {...this.props}
-                                          renderDepth={this.props.renderDepth + 1}
+                                          {...this._props}
+                                          renderDepth={this._props.renderDepth + 1}
                                           Document={pushpin}
                                           PanelWidth={returnOne}
                                           PanelHeight={returnOne}
@@ -800,7 +800,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                     </div>
                     {/* <MapBoxInfoWindow
                                     key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
-                                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                                    {...OmitKeys(this._props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                                     place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
                                     markerMap={this.markerMap}
                                     PanelWidth={this.infoWidth}
@@ -814,7 +814,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
-                        {...this.props}
+                        {...this._props}
                         fieldKey={this.fieldKey}
                         Document={this.Document}
                         layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index 135db64e0..ae674d604 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -81,14 +81,14 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
     // semi-Constants
     xMin = 0;
     yMin = 0;
-    xMax = this.props.PanelWidth() * 0.6;
-    yMax = this.props.PanelHeight();
+    xMax = this._props.PanelWidth() * 0.6;
+    yMax = this._props.PanelHeight();
     color = `rgba(0,0,0,0.5)`;
     radius = 50;
     wallPositions: IWallProps[] = [];
 
     @computed get circularMotionRadius() {
-        return (NumCast(this.dataDoc.circularMotionRadius, 150) * this.props.PanelWidth()) / 1000;
+        return (NumCast(this.dataDoc.circularMotionRadius, 150) * this._props.PanelWidth()) / 1000;
     }
     @computed get gravity() {
         return NumCast(this.dataDoc.simulation_gravity, -9.81);
@@ -191,22 +191,22 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
 
     componentDidMount() {
         // Setup and update simulation
-        this._widthDisposer = reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], this.setupSimulation, { fireImmediately: true });
+        this._widthDisposer = reaction(() => [this._props.PanelWidth(), this._props.PanelHeight()], this.setupSimulation, { fireImmediately: true });
 
         // Create walls
         this.wallPositions = [
             { length: 100, xPos: 0, yPos: 0, angleInDegrees: 0 },
             { length: 100, xPos: 0, yPos: 100, angleInDegrees: 0 },
             { length: 100, xPos: 0, yPos: 0, angleInDegrees: 90 },
-            { length: 100, xPos: (this.xMax / this.props.PanelWidth()) * 100, yPos: 0, angleInDegrees: 90 },
+            { length: 100, xPos: (this.xMax / this._props.PanelWidth()) * 100, yPos: 0, angleInDegrees: 90 },
         ];
     }
 
-    componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<FieldViewProps>>) {
+    componentDidUpdate(prevProps: Readonly<FieldViewProps>) {
         super.componentDidUpdate(prevProps);
-        if (this.xMax !== this.props.PanelWidth() * 0.6 || this.yMax != this.props.PanelHeight()) {
-            this.xMax = this.props.PanelWidth() * 0.6;
-            this.yMax = this.props.PanelHeight();
+        if (this.xMax !== this._props.PanelWidth() * 0.6 || this.yMax != this._props.PanelHeight()) {
+            this.xMax = this._props.PanelWidth() * 0.6;
+            this.yMax = this._props.PanelHeight();
             this.setupSimulation();
         }
     }
@@ -632,7 +632,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
 
     // Default setup for pendulum simulation
     setupPendulum = () => {
-        const length = (300 * this.props.PanelWidth()) / 1000;
+        const length = (300 * this._props.PanelWidth()) / 1000;
         const angle = 30;
         const x = length * Math.cos(((90 - angle) * Math.PI) / 180);
         const y = length * Math.sin(((90 - angle) * Math.PI) / 180);
@@ -808,8 +808,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         const commonWeightProps = {
             pause: this.pause,
             paused: BoolCast(this.dataDoc.simulation_paused),
-            panelWidth: this.props.PanelWidth,
-            panelHeight: this.props.PanelHeight,
+            panelWidth: this._props.PanelWidth,
+            panelHeight: this._props.PanelHeight,
             resetRequest: this.resetRequest,
             xMax: this.xMax,
             xMin: this.xMin,
@@ -853,9 +853,9 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                                     <div
                                         style={{
                                             position: 'fixed',
-                                            left: 0.1 * this.props.PanelWidth() + 'px',
-                                            top: 0.95 * this.props.PanelHeight() + 'px',
-                                            width: 0.5 * this.props.PanelWidth() + 'px',
+                                            left: 0.1 * this._props.PanelWidth() + 'px',
+                                            top: 0.95 * this._props.PanelHeight() + 'px',
+                                            width: 0.5 * this._props.PanelWidth() + 'px',
                                         }}>
                                         <LinearProgress />
                                     </div>
@@ -923,8 +923,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
                     </div>
                     <div
                         className="mechanicsSimulationEquationContainer"
-                        onWheel={e => this.props.isContentActive() && e.stopPropagation()}
-                        style={{ overflow: 'auto', height: `${Math.max(1, 800 / this.props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this.props.PanelWidth() / 850)})` }}>
+                        onWheel={e => this._props.isContentActive() && e.stopPropagation()}
+                        style={{ overflow: 'auto', height: `${Math.max(1, 800 / this._props.PanelWidth()) * 100}%`, transform: `scale(${Math.min(1, this._props.PanelWidth() / 850)})` }}>
                         <div className="mechanicsSimulationControls">
                             <Stack direction="row" spacing={1}>
                                 {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && (
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 658cfb1ca..c04a81f7d 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -31,7 +31,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
 
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         Doc.SetNativeWidth(this.dataDoc, 1280);
         Doc.SetNativeHeight(this.dataDoc, 720);
     }
@@ -49,7 +49,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.dataDoc[this.fieldKey + '_duration'] = this.videoDuration;
 
         this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
-        this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client);
+        this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client);
         this.dataDoc[this.fieldKey + '_recorded'] = true;
         // stringify the presentation and store it
         if (presentation?.movements) {
@@ -135,7 +135,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     public static addRecToWorkspace(value: RecordingBox) {
         let ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView);
-        (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.Document);
+        (ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document);
         Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
         Doc.RemFromMyOverlay(value.Document);
         Doc.UserDoc().currentRecording = undefined;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 79ed69cdd..36527c311 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -47,8 +47,8 @@ declare class MediaRecorder {
 //     _mesh: any = undefined;
 
 //     render() {
-//         const topLeft = [this.props.x, this.props.y];
-//         const raised = this.props.raised;
+//         const topLeft = [this._props.x, this._props.y];
+//         const raised = this._props.raised;
 //         const find = (raised: { coord: Vector2, off: Vector3 }[], what: Vector2) => raised.find(r => r.coord.x === what.x && r.coord.y === what.y);
 //         const tl1 = find(raised, new Vector2(topLeft[0], topLeft[1] + 1));
 //         const tl2 = find(raised, new Vector2(topLeft[0] + 1, topLeft[1] + 1));
@@ -69,11 +69,11 @@ declare class MediaRecorder {
 //         const normals = new Float32Array(quad_normals);
 //         const uvs = new Float32Array(quad_uvs); // Each vertex has one uv coordinate for texture mapping
 //         const indices = new Uint32Array(quad_indices);          // Use the four vertices to draw the two triangles that make up the square.
-//         const popOut = () => NumCast(this.props.Document.popOut);
-//         const popOff = () => NumCast(this.props.Document.popOff);
+//         const popOut = () => NumCast(this.Document.popOut);
+//         const popOff = () => NumCast(this.Document.popOff);
 //         return (
 //             <mesh key={`mesh${topLeft[0]}${topLeft[1]}`} onClick={action(async e => {
-//                 this.props.setRaised([
+//                 this._props.setRaised([
 //                     { coord: new Vector2(topLeft[0], topLeft[1]), off: new Vector3(-popOff(), -popOff(), popOut()) },
 //                     { coord: new Vector2(topLeft[0] + 1, topLeft[1]), off: new Vector3(popOff(), -popOff(), popOut()) },
 //                     { coord: new Vector2(topLeft[0], topLeft[1] + 1), off: new Vector3(-popOff(), popOff(), popOut()) },
@@ -99,7 +99,7 @@ declare class MediaRecorder {
 //                     r?.setAttribute('uv', new BufferAttribute(uvs, 2));
 //                     r?.setIndex(new BufferAttribute(indices, 1));
 //                 }} />
-//                 {!this._videoRef ? <meshStandardMaterial color={this.props.color} /> :
+//                 {!this._videoRef ? <meshStandardMaterial color={this._props.color} /> :
 //                     <meshBasicMaterial >
 //                         <videoTexture attach="map" args={[this._videoRef]} />
 //                     </meshBasicMaterial>}
@@ -118,7 +118,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     @observable private _videoRef: HTMLVideoElement | null = null;
     @observable _screenCapture = false;
     @computed get recordingStart() {
-        return Cast(this.dataDoc[this.props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
+        return Cast(this.dataDoc[this._props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
     }
 
     constructor(props: any) {
@@ -144,8 +144,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     componentDidMount() {
         this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
-        this.props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
-        // this.layoutDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }),
+        this._props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+        // this.layoutDoc.videoWall && reaction(() => ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }),
         //     ({ width, height }) => {
         //         if (this._camera) {
         //             const angle = -Math.abs(1 - width / height);
@@ -201,7 +201,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         //     let count = 0;
         //     numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push(
         //         <VideoTile doc={this.layoutDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />)));
-        //     return <Canvas key="canvas" id="CANCAN" style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }} gl={{ antialias: false }} colorManagement={false} onCreated={props => {
+        //     return <Canvas key="canvas" id="CANCAN" style={{ width: this._props.PanelWidth(), height: this._props.PanelHeight() }} gl={{ antialias: false }} colorManagement={false} onCreated={props => {
         //         this._camera = props.camera;
         //         props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2);
         //         props.camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0);
@@ -224,15 +224,15 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             this._audioRec.onstop = async (e: any) => {
                 const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file })));
                 if (!(result instanceof Error)) {
-                    this.dataDoc[this.props.fieldKey + '_audio'] = new AudioField(result.accessPaths.agnostic.client);
+                    this.dataDoc[this._props.fieldKey + '_audio'] = new AudioField(result.accessPaths.agnostic.client);
                 }
             };
             this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
             this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
             const vid_chunks: any = [];
             this._videoRec.onstart = () => {
-                if (this.dataDoc[this.props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
-                this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date());
+                if (this.dataDoc[this._props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
+                this.dataDoc[this._props.fieldKey + '_recordingStart'] = new DateField(new Date());
             };
             this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
             this._videoRec.onstop = async (e: any) => {
@@ -252,7 +252,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey);
                     this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined;
                     this.layoutDoc._layout_fitWidth = undefined;
-                    this.dataDoc[this.props.fieldKey] = new VideoField(result.accessPaths.agnostic.client);
+                    this.dataDoc[this._props.fieldKey] = new VideoField(result.accessPaths.agnostic.client);
                 } else alert('video conversion failed');
             };
             this._audioRec.start();
@@ -287,8 +287,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
         this.dataDoc[this.fieldKey + '_dictation'] = dictationText;
     };
-    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth();
-    formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
+    videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this._props.PanelWidth();
+    formattedPanelHeight = () => Math.max(0, this._props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
         return (
@@ -296,14 +296,14 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                 <div className="videoBox-viewer">
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
-                            {...this.props}
+                            {...this._props}
                             setContentView={emptyFunction}
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
                             PanelHeight={this.videoPanelHeight}
-                            PanelWidth={this.props.PanelWidth}
-                            focus={this.props.focus}
-                            isSelected={this.props.isSelected}
+                            PanelWidth={this._props.PanelWidth}
+                            focus={this._props.focus}
+                            isSelected={this._props.isSelected}
                             isAnnotationOverlay={true}
                             select={emptyFunction}
                             isContentActive={returnFalse}
@@ -313,8 +313,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
                             addDocument={returnFalse}
-                            ScreenToLocalTransform={this.props.ScreenToLocalTransform}
-                            renderDepth={this.props.renderDepth + 1}>
+                            ScreenToLocalTransform={this._props.ScreenToLocalTransform}
+                            renderDepth={this._props.renderDepth + 1}>
                             <>
                                 {this.threed}
                                 {this.content}
@@ -324,7 +324,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     <div style={{ background: SettingsManager.userColor, position: 'relative', height: this.formattedPanelHeight() }}>
                         {!(this.dataDoc[this.fieldKey + '_dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
-                                {...this.props}
+                                {...this._props}
                                 Document={DocCast(this.dataDoc[this.fieldKey + '_dictation'])}
                                 fieldKey={'text'}
                                 PanelHeight={this.formattedPanelHeight}
@@ -337,12 +337,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 removeDocument={returnFalse}
                                 moveDocument={returnFalse}
                                 addDocument={returnFalse}
-                                renderDepth={this.props.renderDepth + 1}
+                                renderDepth={this._props.renderDepth + 1}
                             />
                         )}
                     </div>
                 </div>
-                {!this.props.isSelected() ? null : (
+                {!this._props.isSelected() ? null : (
                     <div className="screenshotBox-uiButtons" style={{ background: SettingsManager.userColor }}>
                         <div className="screenshotBox-recorder" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userVariantColor }} key="snap" onPointerDown={this.toggleRecording}>
                             <FontAwesomeIcon icon="file" size="lg" />
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 8e506ec64..73ad3a004 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -60,7 +60,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         super(props);
         makeObservable(this);
         if (!this.compileParams.length) {
-            const params = ScriptCast(this.dataDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
+            const params = ScriptCast(this.dataDoc[this._props.fieldKey])?.script.options.params as { [key: string]: any };
             if (params) {
                 this.compileParams = Array.from(Object.keys(params))
                     .filter(p => !p.startsWith('_'))
@@ -116,7 +116,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     @action
     componentDidMount() {
-        this.props.setContentView?.(this);
+        this._props.setContentView?.(this);
         this.rawText = this.rawScript;
         const observer = new _global.ResizeObserver(
             action((entries: any) => {
@@ -294,7 +294,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     onCopy = () => {
         const copy = Doc.MakeCopy(this.Document, true);
         copy.x = NumCast(this.Document.x) + NumCast(this.dataDoc._width);
-        this.props.addDocument?.(copy);
+        this._props.addDocument?.(copy);
     };
 
     // adds option to create a copy to the context menu
@@ -310,7 +310,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         const nameInput = <textarea className="scriptingBox-textarea-inputs" onChange={e => (this.functionName = e.target.value)} placeholder="enter name here" value={this.functionName} />;
 
         return (
-            <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
+            <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
                 <div className="scriptingBox-wrapper" style={{ maxWidth: '100%' }}>
                     <div className="container" style={{ maxWidth: '100%' }}>
                         <div className="descriptor" style={{ textAlign: 'center', display: 'inline-block', maxWidth: '100%' }}>
@@ -687,7 +687,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         );
 
         return (
-            <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
+            <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
                 <div className="scriptingBox-wrapper">
                     {this.renderScriptingBox}
                     {definedParameters}
@@ -745,7 +745,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
     // inputs UI for params which allows you to set values for each displayed in a list
     renderParamsInputs() {
         return (
-            <div className="scriptingBox-inputDiv" onPointerDown={e => this.props.isSelected() && e.stopPropagation()}>
+            <div className="scriptingBox-inputDiv" onPointerDown={e => this._props.isSelected() && e.stopPropagation()}>
                 {!this.compileParams.length || !this.paramsNames ? null : (
                     <div className="scriptingBox-plist">
                         {this.paramsNames.map((parameter: string, i: number) => (
@@ -812,7 +812,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         TraceMobx();
         return (
             <div className={`scriptingBox`} onContextMenu={this.specificContextMenu} onPointerUp={!this._function ? this.suggestionPos : undefined}>
-                <div className="scriptingBox-outerDiv" onWheel={e => this.props.isSelected() && e.stopPropagation()}>
+                <div className="scriptingBox-outerDiv" onWheel={e => this._props.isSelected() && e.stopPropagation()}>
                     {this._paramSuggestion ? (
                         <div className="boxed" ref={this._suggestionRef} style={{ left: this._suggestionBoxX + 20, top: this._suggestionBoxY - 15, display: 'inline' }}>
                             {' '}
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 3e46ff4e6..7fd799952 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -109,7 +109,7 @@ export class AudioWaveform extends ObservableReactComponent<AudioWaveformProps>
                     progressColor={Colors.MEDIUM_BLUE_ALT}
                     progress={this._props.progress ?? 1}
                     barWidth={200 / this.audioBuckets.length}
-                    //gradientColors={this.props.gradientColors}
+                    //gradientColors={this._props.gradientColors}
                     peaks={this.audioBuckets}
                     width={(this._props.PanelWidth ?? 0) * window.devicePixelRatio}
                     height={this.waveHeight * window.devicePixelRatio}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 1002ee403..5a13fa8b4 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -177,7 +177,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
     };
 
     componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.());
-    isContentActive = () => this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.();
+    isContentActive = () => this.props.tbox._props.isSelected() || this.props.tbox.isAnyChildContentActive?.();
 
     render() {
         return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : (
@@ -205,21 +205,21 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
                     removeDocument={this.removeDoc}
                     isDocumentActive={returnFalse}
                     isContentActive={this.isContentActive}
-                    styleProvider={this._textBox.props.styleProvider}
-                    docViewPath={this._textBox.props.docViewPath}
+                    styleProvider={this._textBox._props.styleProvider}
+                    docViewPath={this._textBox._props.docViewPath}
                     ScreenToLocalTransform={this.getDocTransform}
-                    addDocTab={this._textBox.props.addDocTab}
+                    addDocTab={this._textBox._props.addDocTab}
                     pinToPres={returnFalse}
-                    renderDepth={this._textBox.props.renderDepth + 1}
+                    renderDepth={this._textBox._props.renderDepth + 1}
                     PanelWidth={this._finalLayout[Width]}
                     PanelHeight={this._finalLayout[Height]}
                     focus={this.outerFocus}
                     whenChildContentsActiveChanged={this.props.tbox.whenChildContentsActiveChanged}
                     bringToFront={emptyFunction}
                     dontRegisterView={false}
-                    childFilters={this.props.tbox?.props.childFilters}
-                    childFiltersByRanges={this.props.tbox?.props.childFiltersByRanges}
-                    searchFilterDocs={this.props.tbox?.props.searchFilterDocs}
+                    childFilters={this.props.tbox?._props.childFilters}
+                    childFiltersByRanges={this.props.tbox?._props.childFiltersByRanges}
+                    searchFilterDocs={this.props.tbox?._props.searchFilterDocs}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 7e655531e..b786c5ffb 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -65,8 +65,8 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
 
     constructor(props: any) {
         super(props);
-        this._fieldKey = this.props.fieldKey;
-        this._textBoxDoc = this.props.tbox.props.Document;
+        this._fieldKey = props.fieldKey;
+        this._textBoxDoc = props.tbox.Document;
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3b31f2d17..5858c3b11 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -186,7 +186,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveAlignment() {
-        if (this.view && this.TextView?.props.rootSelected?.()) {
+        if (this.view && this.TextView?._props.rootSelected?.()) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
                 if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) {
@@ -199,7 +199,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveListStyle() {
-        if (this.view && this.TextView?.props.rootSelected?.()) {
+        if (this.view && this.TextView?._props.rootSelected?.()) {
             const path = (this.view.state.selection.$from as any).path;
             for (let i = 0; i < path.length; i += 3) {
                 if (path[i].type === this.view.state.schema.nodes.ordered_list) {
@@ -219,7 +219,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         const activeSizes = new Set<string>();
         const activeColors = new Set<string>();
         const activeHighlights = new Set<string>();
-        if (this.view && this.TextView?.props.rootSelected?.()) {
+        if (this.view && this.TextView?._props.rootSelected?.()) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
             const marks: Mark[] = [...(state.storedMarks ?? [])];
@@ -254,7 +254,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     //finds all active marks on selection in given group
     getActiveMarksOnSelection() {
         let activeMarks: MarkType[] = [];
-        if (!this.view || !this.TextView?.props.rootSelected?.()) return activeMarks;
+        if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks;
 
         const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
         if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
@@ -447,7 +447,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered);
     };
     align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
-        if (this.TextView?.props.rootSelected?.()) {
+        if (this.TextView?._props.rootSelected?.()) {
             var tr = view.state.tr;
             view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => {
                 if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) {
@@ -581,7 +581,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return (this.view as any)?.TextView as FormattedTextBox;
     }
     get TextViewFieldKey() {
-        return this.TextView?.props.fieldKey;
+        return this.TextView?._props.fieldKey;
     }
 
     @action setActiveHighlight(color: string) {
@@ -774,11 +774,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         //         <div className="collectionMenu-divider" key="divider 3" />
         //         {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
         //             this.activeFontSize = val;
-        //             SelectionManager.Views.map(dv => dv.props.Document._text_fontSize = val);
+        //             SelectionManager.Views.map(dv => dv.Document._text_fontSize = val);
         //         })),
         //         this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
         //             this.activeFontFamily = val;
-        //             SelectionManager.Views.map(dv => dv.props.Document._text_fontFamily = val);
+        //             SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val);
         //         })),
         //         <div className="collectionMenu-divider" key="divider 4" />,
         //         this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index b573f7c48..1a92acea1 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -13,12 +13,12 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(ImportElementBox, fieldKey);
     }
 
-    screenToLocalXf = () => this.props.ScreenToLocalTransform().scale(1 * (this.props.NativeDimScaling?.() || 1));
+    screenToLocalXf = () => this._props.ScreenToLocalTransform().scale(1 * (this._props.NativeDimScaling?.() || 1));
     @computed get mainItem() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
                 <DocumentView
-                    {...this.props} //
+                    {...this._props} //
                     LayoutTemplateString={undefined}
                     Document={this.Document}
                     isContentActive={returnFalse}
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 5ea9e9979..a1f5ce703 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -12,6 +12,7 @@ import { OpenWhere } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { AnchorMenu } from './AnchorMenu';
 import './Annotation.scss';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 
 interface IAnnotationProps extends FieldViewProps {
     anno: Doc;
@@ -21,12 +22,16 @@ interface IAnnotationProps extends FieldViewProps {
     pointerEvents?: () => Opt<string>;
 }
 @observer
-export class Annotation extends React.Component<IAnnotationProps> {
+export class Annotation extends ObservableReactComponent<IAnnotationProps> {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
     render() {
         return (
-            <div style={{ display: this.props.anno.textCopied && !Doc.GetBrushHighlightStatus(this.props.anno) ? 'none' : undefined }}>
-                {DocListCast(this.props.anno.text_inlineAnnotations).map(a => (
-                    <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} />
+            <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}>
+                {DocListCast(this._props.anno.text_inlineAnnotations).map(a => (
+                    <RegionAnnotation pointerEvents={this._props.pointerEvents} {...this._props} document={a} key={a[Id]} />
                 ))}
             </div>
         );
@@ -38,23 +43,23 @@ interface IRegionAnnotationProps extends IAnnotationProps {
     pointerEvents?: () => Opt<string>;
 }
 @observer
-class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
+class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> {
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
 
     @computed get annoTextRegion() {
-        return Cast(this.props.document.annoTextRegion, Doc, null) || this.props.document;
+        return Cast(this._props.document.annoTextRegion, Doc, null) || this._props.document;
     }
 
     @undoBatch
     deleteAnnotation = () => {
-        const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]);
-        this.props.dataDoc[this.props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
+        const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]);
+        this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion));
         AnchorMenu.Instance.fadeOut(true);
-        this.props.select(false);
+        this._props.select(false);
     };
 
     @undoBatch
-    pinToPres = () => this.props.pinToPres(this.annoTextRegion, {});
+    pinToPres = () => this._props.pinToPres(this.annoTextRegion, {});
 
     @undoBatch
     makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle);
@@ -65,7 +70,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         const trail = DocCast(anchor.presentationTrail);
         if (trail) {
             Doc.ActivePresentation = trail;
-            this.props.addDocTab(trail, OpenWhere.replaceRight);
+            this._props.addDocTab(trail, OpenWhere.replaceRight);
         }
     };
 
@@ -94,9 +99,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     };
 
     @computed get linkHighlighted() {
-        for (const link of LinkManager.Instance.getAllDirectLinks(this.props.document)) {
-            const a1 = LinkManager.getOppositeAnchor(link, this.props.document);
-            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this.props.document))) return true;
+        for (const link of LinkManager.Instance.getAllDirectLinks(this._props.document)) {
+            const a1 = LinkManager.getOppositeAnchor(link, this._props.document);
+            if (a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, this._props.document))) return true;
         }
     }
 
@@ -107,24 +112,24 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
                 className="htmlAnnotation"
                 ref={this._mainCont}
                 onPointerEnter={action(() => {
-                    Doc.BrushDoc(this.props.anno);
-                    this.props.showInfo?.(this.props.anno);
+                    Doc.BrushDoc(this._props.anno);
+                    this._props.showInfo?.(this._props.anno);
                 })}
                 onPointerLeave={action(() => {
-                    Doc.UnBrushDoc(this.props.anno);
-                    this.props.showInfo?.(undefined);
+                    Doc.UnBrushDoc(this._props.anno);
+                    this._props.showInfo?.(undefined);
                 })}
                 onPointerDown={this.onPointerDown}
                 onContextMenu={this.onContextMenu}
                 style={{
-                    left: NumCast(this.props.document.x),
-                    top: NumCast(this.props.document.y),
-                    width: NumCast(this.props.document._width),
-                    height: NumCast(this.props.document._height),
+                    left: NumCast(this._props.document.x),
+                    top: NumCast(this._props.document.y),
+                    width: NumCast(this._props.document._width),
+                    height: NumCast(this._props.document._height),
                     opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined,
-                    pointerEvents: this.props.pointerEvents?.() as any,
+                    pointerEvents: this._props.pointerEvents?.() as any,
                     outline: brushed === Doc.DocBrushStatus.unbrushed && this.linkHighlighted ? 'solid 1px lightBlue' : undefined,
-                    backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this.props.document.backgroundColor),
+                    backgroundColor: brushed === Doc.DocBrushStatus.highlighted ? 'orange' : StrCast(this._props.document.backgroundColor),
                 }}
             />
         );
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 14187833f..4d29573d4 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -122,10 +122,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
     @undoBatch
     makeLink = action((linkTo: Doc) => {
-        const linkFrom = this.props.linkCreateAnchor?.();
+        const linkFrom = this._props.linkCreateAnchor?.();
         if (linkFrom) {
             const link = DocUtils.MakeLink(linkFrom, linkTo, {});
-            link && this.props.linkCreated?.(link);
+            link && this._props.linkCreated?.(link);
         }
     });
 
@@ -292,7 +292,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
         const query = StrCast(this._searchString);
         Doc.SetSearchQuery(query);
-        if (!this.props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
+        if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true));
         this._results.clear();
 
         if (query) {
@@ -375,13 +375,13 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     render() {
         var validResults = 0;
 
-        const isLinkSearch: boolean = this.props.linkSearch;
+        const isLinkSearch: boolean = this._props.linkSearch;
 
         const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
 
         const resultsJSX = Array();
 
-        const fromDoc = this.props.linkFrom?.();
+        const fromDoc = this._props.linkFrom?.();
 
         sortedResults.forEach(result => {
             var className = 'searchBox-results-scroll-view-result';
diff --git a/src/client/views/selectedDoc/SelectedDocView.tsx b/src/client/views/selectedDoc/SelectedDocView.tsx
index daacb368b..39e778b76 100644
--- a/src/client/views/selectedDoc/SelectedDocView.tsx
+++ b/src/client/views/selectedDoc/SelectedDocView.tsx
@@ -1,14 +1,14 @@
+import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ListBox } from 'browndash-components';
 import { computed } from 'mobx';
 import { observer } from 'mobx-react';
-import * as React from 'react';
-import { emptyFunction } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
-import { SettingsManager } from '../../util/SettingsManager';
 import { DocFocusOptions } from '../nodes/DocumentView';
+import { emptyFunction } from '../../../Utils';
+import { SettingsManager } from '../../util/SettingsManager';
 
 export interface SelectedDocViewProps {
     selectedDocs: Doc[];
-- 
cgit v1.2.3-70-g09d2


From 2ea594a4c1ff221200e0795fb07d30b603177a67 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 3 Jan 2024 23:44:28 -0500
Subject: cleaned up screenToLocalTransform in several places by making it
 additional methods.  cleaned up styleProider api a bit to take
 DocumentViewInternalProps, not DocumentViewProps..

---
 src/client/util/HypothesisUtils.ts                 |   2 +-
 src/client/views/DocComponent.tsx                  |  10 ++
 src/client/views/DocumentButtonBar.tsx             |   2 +-
 src/client/views/DocumentDecorations.tsx           |  16 +--
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/InkingStroke.tsx                  |  23 ++---
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/StyleProvider.tsx                 |  13 +--
 .../views/collections/CollectionCarouselView.tsx   |   5 +-
 .../views/collections/CollectionNoteTakingView.tsx |  18 ++--
 .../collections/CollectionStackedTimeline.tsx      |  11 ++-
 .../views/collections/CollectionStackingView.tsx   |  12 +--
 .../views/collections/CollectionTimeView.tsx       |   1 -
 .../views/collections/CollectionTreeView.tsx       |   4 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TabDocView.tsx        |   7 +-
 src/client/views/collections/TreeView.tsx          |  18 ++--
 .../CollectionFreeFormLinkView.tsx                 |  10 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  94 +++++++++---------
 .../collectionGrid/CollectionGridView.tsx          |   6 +-
 .../CollectionMulticolumnView.tsx                  |   2 +-
 .../CollectionMultirowView.tsx                     |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  21 ++--
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   9 +-
 src/client/views/nodes/ComparisonBox.tsx           |   6 +-
 .../views/nodes/DataVizBox/components/TableBox.tsx |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |  48 +++++----
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/client/views/nodes/LinkBox.tsx                 |  10 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   8 +-
 src/client/views/nodes/MapBox/MapBox2.tsx          |   2 +-
 .../views/nodes/MapboxMapBox/MapboxContainer.tsx   |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   1 -
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/WebBox.tsx                  |  12 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 109 +--------------------
 .../views/nodes/importBox/ImportElementBox.tsx     |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   4 +-
 src/client/views/nodes/trails/PresElementBox.tsx   |   4 +-
 src/client/views/pdf/PDFViewer.tsx                 |  15 +--
 44 files changed, 230 insertions(+), 315 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/components/TableBox.tsx')

diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index 0ae23d793..f46c2d431 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -180,7 +180,7 @@ export namespace Hypothesis {
                 })
             );
             const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
-            const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0);
             targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
         }, 300);
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index b5bd17572..dfc298840 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -12,12 +12,14 @@ import { DocumentManager } from '../util/DocumentManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { DocumentView } from './nodes/DocumentView';
+import { Transform } from '../util/Transform';
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 export interface DocComponentProps {
     Document: Doc;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
+    ScreenToLocalTransform: () => Transform;
 }
 export function DocComponent<P extends DocComponentProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
@@ -26,6 +28,8 @@ export function DocComponent<P extends DocComponentProps>() {
             makeObservable(this);
         }
 
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -50,10 +54,13 @@ interface ViewBoxBaseProps {
     fieldKey: string;
     isSelected: () => boolean;
     isContentActive: () => boolean | undefined;
+    ScreenToLocalTransform: () => Transform;
     renderDepth: number;
 }
 export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
         get Document() {
             return this._props.Document;
@@ -83,6 +90,7 @@ export interface ViewBoxAnnotatableProps {
     isContentActive: () => boolean | undefined;
     select: (isCtrlPressed: boolean) => void;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
+    ScreenToLocalTransform: () => Transform;
     isSelected: () => boolean;
     renderDepth: number;
     isAnnotationOverlay?: boolean;
@@ -94,6 +102,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             makeObservable(this);
         }
 
+        ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+
         @observable _annotationKeySuffix = () => 'annotations';
         @observable _isAnyChildContentActive = false;
         //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e6d53e727..bb28d9ec9 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -305,7 +305,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         if (this._dragRef.current) {
             const dragDocView = this.view0!;
             const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
-            const [left, top] = dragDocView.screenToNativeLocalTransform().inverse().transformPoint(0, 0);
+            const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
             dragData.defaultDropAction = 'embed';
             dragData.canEmbed = true;
             DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 583a7c447..ced8799bf 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,7 +77,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 const center = {x: (this.Bounds.x+this.Bounds.r)/2, y: (this.Bounds.y+this.Bounds.b)/2};
                 const {x,y} = Utils.rotPt(e.clientX - center.x, 
                                           e.clientY - center.y, 
-                                          NumCast(SelectionManager.Views.lastElement()?.screenToNativeLocalTransform().Rotate));
+                                          NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
                 (this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
                     (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x || 
                      this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
@@ -208,7 +208,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             SelectionManager.Views.map(dv => dv.Document),
             dragDocView._props.dropAction
         );
-        dragData.offset = dragDocView.screenToNativeLocalTransform().transformDirection(e.x - left, e.y - top);
+        dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top);
         dragData.moveDocument = dragDocView._props.moveDocument;
         dragData.removeDocument = dragDocView._props.removeDocument;
         dragData.isDocDecorationMove = true;
@@ -353,7 +353,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     };
 
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
-        const newloccentern = seldocview.screenToNativeLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
+        const newloccentern = seldocview.screenToContentsTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
         const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.Document._rotation) / 180) * Math.PI);
         seldocview.Document.rotation_centerX = final.x / NumCast(seldocview.layoutDoc._width);
@@ -385,7 +385,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const seldocview = SelectionManager.Views[0];
         SelectionManager.Views.forEach(dv => {
             const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI;
-            const localRotCtr = dv._props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
+            const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
             const unrotatedDocPos = { x: NumCast(dv.Document.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.Document.y) + localRotCtrOffset[1] - startRotCtr.y };
@@ -462,7 +462,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             dot = (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
             return [a[0] + atob[0] * t, a[1] + atob[1] * t];
         };
-        const tl = docView.screenToNativeLocalTransform().inverse().transformPoint(0, 0);
+        const tl = docView.screenToContentsTransform().inverse().transformPoint(0, 0);
         return project([e.clientX + this._offset.x, e.clientY + this._offset.y], tl, [tl[0] + fixedAspect, tl[1] + 1]);
     };
     onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => {
@@ -530,7 +530,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             doc.xPadding = NumCast(doc.xPadding) * scale.x;
             doc.yPadding = NumCast(doc.yPadding) * scale.y;
         } else {
-            const refCent = docView.screenToNativeLocalTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
+            const refCent = docView.screenToContentsTransform().transformPoint(refPt[0], refPt[1]); // fixed reference point for resize (ie, a point that doesn't move)
             const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight];
             const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)];
 
@@ -632,7 +632,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
     @computed get rotCenter() {
         const lastView = SelectionManager.Views.lastElement();
         if (lastView) {
-            const invXf = lastView.screenToNativeLocalTransform().inverse();
+            const invXf = lastView.screenToContentsTransform().inverse();
             const seldoc = lastView.layoutDoc;
             const loccenter = Utils.rotPt(NumCast(seldoc.rotation_centerX) * NumCast(seldoc._width), NumCast(seldoc.rotation_centerY) * NumCast(seldoc._height), invXf.Rotate);
             return invXf.transformPoint(loccenter.x + NumCast(seldoc._width) / 2, loccenter.y + NumCast(seldoc._height) / 2);
@@ -691,7 +691,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
         const bounds = this.ClippedBounds;
         const useLock = bounds.r - bounds.x > 135 && seldocview.CollectionFreeFormDocumentView;
         const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && seldocview.CollectionFreeFormDocumentView; // when do we want an object to not rotate?
-        const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToNativeLocalTransform().inverse().RotateDeg : 0;
+        const rotation = SelectionManager.Views.length == 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0;
 
         // Radius constants
         const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 65aadc148..b1b04237c 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -385,7 +385,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil
 
     get elements() {
         const selView = GestureOverlay.DownDocView;
-        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?._props.ScreenToLocalTransform().Scale || 1);
+        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.screenToViewTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
         B.left = B.left - width / 2;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 4015fb321..77b831e51 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -301,7 +301,7 @@ export class KeyManager {
             case 'x':
                 if (SelectionManager.Views.length) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
                     SelectionManager.Views.length && navigator.clipboard.writeText(text);
                     DocumentDecorations.Instance.onCloseClick(true);
@@ -312,7 +312,7 @@ export class KeyManager {
             case 'c':
                 if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) {
                     const bds = DocumentDecorations.Instance.Bounds;
-                    const pt = SelectionManager.Views[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
+                    const pt = SelectionManager.Views[0].screenToViewTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
                     const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views.map(dv => dv.Document[Id]).join(':');
                     SelectionManager.Views.length && navigator.clipboard.writeText(text);
                     stopPropagation = false;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 95c677845..c652e8966 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -45,6 +45,7 @@ import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
+import { Transform } from '../util/Transform';
 const { default: { INK_MASK_SIZE } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -73,10 +74,6 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         Object.keys(this._disposers).forEach(key => this._disposers[key]());
     }
 
-    // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke
-    // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc)
-    screenToLocal = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
-
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const subAnchor = this._subContentView?.getAnchor?.(addAsAnnotation);
         if (subAnchor !== this.Document && subAnchor) return subAnchor;
@@ -129,7 +126,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -181,7 +178,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     ptFromScreen = (scrPt: { X: number; Y: number }) => {
         const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
-        const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y);
+        const docPt = this.ScreenToLocalBoxXf().transformPoint(scrPt.X, scrPt.Y);
         const inkPt = {
             X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft,
             Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop,
@@ -199,7 +196,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
             Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2,
         };
-        const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y);
+        const scrPt = this.ScreenToLocalBoxXf().inverse().transformPoint(docPt.X, docPt.Y);
         return { X: scrPt[0], Y: scrPt[1] };
     };
 
@@ -213,7 +210,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => {
         const { inkData } = this.inkScaledData();
         const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
-        return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale };
+        return { nearestPt, distance: distance * this.ScreenToLocalBoxXf().inverse().Scale };
     };
 
     /**
@@ -250,7 +247,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         const screenPts = inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -275,7 +272,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
         return inkData
             .map(point =>
-                this.screenToLocal()
+                this.ScreenToLocalBoxXf()
                     .inverse()
                     .transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)
             )
@@ -291,9 +288,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
     componentUI = (boundsLeft: number, boundsTop: number) => {
         const inkDoc = this.Document;
         const { inkData, inkStrokeWidth } = this.inkScaledData();
-        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
+        const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.ScreenToLocalBoxXf().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke
 
-        const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
+        const screenInkWidth = this.ScreenToLocalBoxXf().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth);
 
         const startMarker = StrCast(this.layoutDoc.stroke_startMarker);
         const endMarker = StrCast(this.layoutDoc.stroke_endMarker);
@@ -329,7 +326,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                     false
                 )}
                 <InkControlPtHandles inkView={this} inkDoc={inkDoc} inkCtrlPoints={inkData} screenCtrlPoints={this.screenCtrlPts} nearestScreenPt={this.nearestScreenPt} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} />
-                <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.screenToLocal} />
+                <InkTangentHandles inkView={this} inkDoc={inkDoc} screenCtrlPoints={this.screenCtrlPts} screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} ScreenToLocalTransform={this.ScreenToLocalBoxXf} />
             </div>
         );
     };
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 9f3786d8e..ed09d3bf3 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -158,7 +158,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
     //    4) reattach the vector to the center of the bounding box
     getTransformedScreenPt = (down: number[]) => {
         const marqueeContainer = this.props.marqueeContainer;
-        const containerXf = this.props.isNativeScaled ? this.props.docView().screenToNativeLocalTransform() : this.props.docView().props.ScreenToLocalTransform();
+        const containerXf = this.props.isNativeScaled ? this.props.docView().screenToContentsTransform() : this.props.docView().screenToViewTransform();
         const boundingRect = marqueeContainer.getBoundingClientRect();
         const center = { x: boundingRect.x + boundingRect.width / 2, y: boundingRect.y + boundingRect.height / 2 };
         const downVec = Utils.rotPt(down[0] - center.x,  
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 835e61d55..ce69dcc41 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -20,7 +20,7 @@ import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
-import { DocumentViewProps } from './nodes/DocumentView';
+import { DocumentViewInternalProps, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PropertiesView } from './PropertiesView';
@@ -37,7 +37,6 @@ export enum StyleProp {
     BackgroundColor = 'backgroundColor', // background color of a document view
     FillColor = 'fillColor', // fill color of an ink stroke or shape
     WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
-    HideLinkBtn = 'hideLinkButton', // hides the blue-dot link button.  used when a document acts like a button
     PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
     Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
     HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
@@ -81,11 +80,10 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
 
 // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
 //
-export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any {
     const remoteDocHeader = 'author;author_date;noMargin';
     const docProps = testDocProps(props) ? props : undefined;
     const fieldProps = testFieldProps(props) ? props : undefined;
-    const selected = property.includes(':selected');
     const isCaption = property.includes(':caption');
     const isAnchor = property.includes(':anchor');
     const isContent = property.includes(':content');
@@ -138,14 +136,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.DocContents:return undefined;
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
         case StyleProp.Opacity:    return docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
-        case StyleProp.HideLinkBtn:return props?.hideLinkButton || (!selected && doc?.layout_hideLinkButton);
         case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(doc?._text_fontSize,   StrCast(Doc.UserDoc().fontSize)));
         case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
         case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
         case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
         case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
         case StyleProp.TitleHeight:
-            return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
+            return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
             return (
                 (doc &&
@@ -171,7 +168,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
             const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView._props, StyleProp.BackgroundColor);
+            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView.docView?._props, StyleProp.BackgroundColor);
             return backColor ? lightOrDark(backColor) : undefined;
         case StyleProp.BorderRounding:
             return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
@@ -373,7 +370,7 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp,
 /**
  * add hide button decorations for the "Dashboards" flyout TreeView
  */
-export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewInternalProps>, property: string) {
     if (doc && property.split(':')[0] === StyleProp.Decorations) {
         return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
             ? null
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index b6acf3153..fb57a668e 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -7,10 +7,11 @@ import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { FieldViewProps } from '../nodes/FieldView';
 
 @observer
 export class CollectionCarouselView extends CollectionSubView() {
@@ -40,7 +41,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         e.stopPropagation();
         this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
     };
-    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewProps>, property: string): any => {
+    captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<DocumentViewInternalProps|FieldViewProps>, property: string): any => {
         // first look for properties on the document in the carousel, then fallback to properties on the container
         const childValue = doc?.['caption-' + property] ? this.props.styleProvider?.(doc, captionProps, property) : undefined;
         return childValue ?? this.props.styleProvider?.(this.layoutDoc, captionProps, property);
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 0744814dd..527b72213 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -18,7 +18,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -195,7 +195,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
-            const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top);
+            const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0 && Math.ceil(this.props.PanelHeight()) < (this._mainCont?.scrollHeight || 0)) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -204,7 +204,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         }
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         switch (property) {
             case StyleProp.BoxShadow:
                 if (doc && DragManager.docsBeingDragged.includes(doc)) {
@@ -282,7 +282,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         const y = this._scroll; // required for document decorations to update when the text box container is scrolled
         const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale);
+        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
 
     // how to get the width of a document. Currently returns the width of the column (minus margins)
@@ -341,10 +341,10 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     onPointerMove = (force: boolean, ex: number, ey: number) => {
         if (this.childDocList?.includes(DragManager.DocDragData?.draggedDocuments?.lastElement() as any) || force || SnappingManager.CanEmbed) {
             // get the current docs for the column based on the mouse's x coordinate
-            const xCoord = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[0] - 2 * this.gridGap;
+            const xCoord = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[0] - 2 * this.gridGap;
             const colDocs = this.getDocsFromXCoord(xCoord);
             // get the index for where you need to insert the doc you are currently dragging
-            const clientY = this.props.ScreenToLocalTransform().transformPoint(ex, ey)[1];
+            const clientY = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[1];
             let dropInd = -1;
             let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
             colDocs.forEach((doc, i) => {
@@ -437,7 +437,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                     if (rowCol[0] <= 0) {
                         docs.splice(0, 0, ...newDocs);
                     } else {
-                        const colDocs = this.getDocsFromXCoord(this.props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0]);
+                        const colDocs = this.getDocsFromXCoord(this.ScreenToLocalBoxXf().transformPoint(de.x, de.y)[0]);
                         const previousDoc = colDocs[rowCol[0] - 1];
                         const previousDocIndex = docs.indexOf(previousDoc);
                         docs.splice(previousDocIndex + 1, 0, ...newDocs);
@@ -546,7 +546,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 yMargin={this.yMargin}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this.props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
                 editableViewProps={this.editableViewProps}
             />
         );
@@ -586,7 +586,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     // setColumnStartXCoords is used to update column widths when using the drag handlers between columns
     @action
     setColumnStartXCoords = (movementXScreen: number, colIndex: number) => {
-        const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0];
+        const movementX = this.ScreenToLocalBoxXf().transformDirection(movementXScreen, 0)[0];
         const leftHeader = this.colHeaderData[colIndex];
         const rightHeader = this.colHeaderData[colIndex + 1];
         leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a63688354..d99b4f9de 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -23,11 +23,12 @@ import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionSubView } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
 import { AudioWaveform } from '../nodes/audio/AudioWaveform';
-import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
+import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
+import { FieldViewProps } from '../nodes/FieldView';
 
 export type CollectionStackedTimelineProps = {
     Play: () => void;
@@ -367,7 +368,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
         if (super.onInternalDrop(e, de)) {
             // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
-            const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+            const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
             const x = localPt[0] - docDragData.offset[0];
             const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
             docDragData.droppedDocuments.forEach(drop => {
@@ -502,7 +503,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         return this._props.PanelWidth() * this.zoomFactor;
     } // subtract size of container border
 
-    dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+    dictationScreenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this.timelineContentHeight);
 
     isContentActive = () => this._props.isSelected() || this._props.isContentActive();
 
@@ -672,7 +673,7 @@ interface StackedTimelineAnchorProps {
     width: number;
     height: number;
     toTimeline: (screen_delta: number, width: number) => number;
-    styleProvider?: (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
+    styleProvider?: StyleProviderFunc;
     playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
     setTime: (time: number) => void;
     startTag: string;
@@ -695,7 +696,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    constructor(props: any) {
+    constructor(props: StackedTimelineAnchorProps) {
         super(props);
         makeObservable(this);
         this._lastTimecode = this._props.currentTimecode();
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 16adf4b96..995f071ca 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -24,7 +24,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -255,7 +255,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const top = found.getBoundingClientRect().top;
-            const localTop = this._props.ScreenToLocalTransform().transformPoint(0, top);
+            const localTop = this.ScreenToLocalBoxXf().transformPoint(0, top);
             if (Math.floor(localTop[1]) !== 0) {
                 let focusSpeed = options.zoomTime ?? 500;
                 smoothScroll(focusSpeed, this._mainCont!, localTop[1] + this._mainCont!.scrollTop, options.easeFunc);
@@ -265,7 +265,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return undefined;
     };
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity && doc) {
             if (this._props.childOpacity) {
                 return this._props.childOpacity();
@@ -370,7 +370,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
         const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
         // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this._props.ScreenToLocalTransform().Scale);
+        return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.ScreenToLocalBoxXf().Scale);
     }
     getDocWidth(d?: Doc) {
         if (!d) return 0;
@@ -567,7 +567,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 yMargin={this.yMargin}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this._props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
             />
         );
     };
@@ -611,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 parent={this}
                 type={type}
                 createDropTarget={this.createDashEventsTarget}
-                screenToLocalTransform={this._props.ScreenToLocalTransform}
+                screenToLocalTransform={this.ScreenToLocalBoxXf}
                 setDocHeight={this.setDocHeight}
             />
         );
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 7bbdb7073..7036ec41c 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -211,7 +211,6 @@ export class CollectionTimeView extends CollectionSubView() {
         Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
         docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
         ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
-        const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
         ContextMenu.Instance.displayMenu(x, y, ':');
     };
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 7f8d42088..18e0b98ef 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -154,7 +154,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         }
     };
 
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this._headerHeight);
+    screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
 
     @action
     remove = (doc: Doc | Doc[]): boolean => {
@@ -312,7 +312,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.dataDoc === null ? null : (
             <div
                 className="collectionTreeView-titleBar"
-                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this._props.ScreenToLocalTransform().Scale))}
+                ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.ScreenToLocalBoxXf().Scale))}
                 key={this.Document[Id]}
                 style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c2f5ab2c0..4a239e4b1 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -112,7 +112,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         return viewField as any as CollectionViewType;
     }
 
-    screenToLocalTransform = () => (this._props.renderDepth ? this._props.ScreenToLocalTransform() : this._props.ScreenToLocalTransform().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
+    screenToLocalTransform = () => (this._props.renderDepth ? this.ScreenToLocalBoxXf() : this.ScreenToLocalBoxXf().scale(this._props.PanelWidth() / this.bodyPanelWidth()));
     // prettier-ignore
     private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index ac0ce7a2a..80808be92 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -29,7 +29,7 @@ import { LightboxView } from '../LightboxView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { Colors } from '../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -37,6 +37,7 @@ import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { FieldViewProps } from '../nodes/FieldView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -426,7 +427,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     ScreenToLocalTransform = () => {
         this._forceInvalidateScreenToLocal;
         const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
-        return CollectionDockingView.Instance?._props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity();
+        return CollectionDockingView.Instance?.ScreenToLocalBoxXf().translate(-translateX, -translateY) ?? Transform.Identity();
     };
     PanelWidth = () => this._panelWidth;
     PanelHeight = () => this._panelHeight;
@@ -527,7 +528,7 @@ class TabMiniThumb extends React.Component<TabMiniThumbProps> {
 }
 @observer
 export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
-    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
                 default:
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index f2ceaa681..01b80e209 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -27,7 +27,7 @@ import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, DocumentViewInternalProps, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -172,6 +172,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         return this._docRef?.IsSelected;
     }
 
+    ScreenToLocalTransform = () => this._props.ScreenToLocalTransform();
+
     childDocList(field: string) {
         const layout = Cast(Doc.LayoutField(this.Document), Doc, null);
         return DocListCast(this._props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.Document[field])));
@@ -447,11 +449,11 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     }
 
     refTransform = (ref: HTMLDivElement | undefined | null) => {
-        if (!ref) return this._props.ScreenToLocalTransform();
+        if (!ref) return this.ScreenToLocalTransform();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ref);
         const outerXf = Utils.GetScreenTransform(this.treeView.MainEle());
-        const offset = this._props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
-        return this._props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+        const offset = this.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+        return this.ScreenToLocalTransform().translate(offset[0], offset[1]);
     };
     docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
     getTransform = () => this.refTransform(this._tref.current);
@@ -511,7 +513,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                     this._props.dragAction,
                     this._props.addDocTab,
                     this.titleStyleProvider,
-                    this._props.ScreenToLocalTransform,
+                    this.ScreenToLocalTransform,
                     this._props.isContentActive,
                     expandedWidth,
                     this._props.renderDepth,
@@ -671,7 +673,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
                                   StrCast(this.Document.childDragAction, this._props.dragAction) as dropActionType,
                                   this._props.addDocTab,
                                   this.titleStyleProvider,
-                                  this._props.ScreenToLocalTransform,
+                                  this.ScreenToLocalTransform,
                                   this._props.isContentActive,
                                   this._props.panelWidth,
                                   this._props.renderDepth,
@@ -865,7 +867,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
             e.preventDefault();
         }
     };
-    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (!doc || doc !== this.Document) return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
         const treeView = this.treeView;
@@ -897,7 +899,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
         }
         return treeView._props.styleProvider?.(doc, props, property);
     };
-    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
         return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6131fe037..5204633ea 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -22,8 +22,6 @@ export interface CollectionFreeFormLinkViewProps {
     LinkDocs: Doc[];
 }
 
-// props.screentolocatransform
-
 @observer
 export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
     @observable _opacity: number = 0;
@@ -42,10 +40,10 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
     componentDidMount() {
         this._anchorDisposer = reaction(
             () => [
-                this._props.A._props.ScreenToLocalTransform(),
+                this._props.A.screenToViewTransform(),
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
-                this._props.B._props.ScreenToLocalTransform(),
+                this._props.B.screenToViewTransform(),
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
                 Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
             ],
@@ -91,7 +89,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
             }
         } else {
             const m = targetAhyperlink.getBoundingClientRect();
-            const mp = A._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+            const mp = A.screenToViewTransform().transformPoint(m.right, m.top + 5);
             const mpx = mp[0] / A._props.PanelWidth();
             const mpy = mp[1] / A._props.PanelHeight();
             if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
@@ -106,7 +104,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent<Collect
             }
         } else {
             const m = targetBhyperlink.getBoundingClientRect();
-            const mp = B._props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
+            const mp = B.screenToViewTransform().transformPoint(m.right, m.top + 5);
             const mpx = mp[0] / B._props.PanelWidth();
             const mpy = mp[1] / B._props.PanelHeight();
             if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 44654c8fe..0128647a4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,7 +38,7 @@ import { ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } fr
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView';
 import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PinProps, PresBox } from '../../nodes/trails/PresBox';
@@ -172,10 +172,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @computed get panZoomXf() {
         return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
     }
-    @computed get screenToLocalXf() {
+    @computed get screenToFreeformContentsXf() {
         return this._props
-            .ScreenToLocalTransform()
-            .scale(this._props.isAnnotationOverlay ? 1 : 1)
+            .ScreenToLocalTransform() //
             .translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
             .transform(this.panZoomXf);
     }
@@ -242,7 +241,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
     PanZoomCenterXf = () =>
         this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
-    ScreenToLocalXf = () => this.screenToLocalXf.copy();
+    ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     isAnyChildContentActive = () => this._props.isAnyChildContentActive();
     addLiveTextBox = (newDoc: Doc) => {
@@ -329,13 +328,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             findDoc(dv => res(dv));
         });
 
-    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
+    internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
         if (!super.onInternalDrop(e, de)) return false;
         const refDoc = docDragData.droppedDocuments[0];
-        const [xpo, ypo] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
-        const z = NumCast(refDoc.z);
-        const x = (z ? xpo : xp) - docDragData.offset[0];
-        const y = (z ? ypo : yp) - docDragData.offset[1];
+        const fromScreenXf = NumCast(refDoc.z) ? this.ScreenToLocalBoxXf() : this.screenToFreeformContentsXf;
+        const [xpo, ypo] = fromScreenXf.transformPoint(de.x, de.y);
+        const x = xpo - docDragData.offset[0];
+        const y = ypo - docDragData.offset[1];
         const zsorted = this.childLayoutPairs
             .map(pair => pair.layout)
             .slice()
@@ -347,7 +346,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
             const d = docDragData.droppedDocuments[i];
             const layoutDoc = Doc.Layout(d);
-            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], this._props.ScreenToLocalTransform().Rotate);
+            const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate);
             if (this.Document._currentFrame !== undefined) {
                 CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
                 const pvals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); // get filled in values (uses defaults when not value is specified) for position
@@ -393,8 +392,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @undoBatch
-    internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData, xp: number, yp: number) {
+    internalAnchorAnnoDrop(e: Event, de: DragManager.DropEvent, annoDragData: DragManager.AnchorAnnoDragData) {
         const dropCreator = annoDragData.dropDocCreator;
+        const [xp, yp] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
         annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => {
             const dropDoc = dropCreator(annotationOn);
             if (dropDoc) {
@@ -408,20 +408,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     @undoBatch
-    internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
+    internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData) {
         if (linkDragData.linkDragView.props.docViewPath().includes(this._props.docViewPath().lastElement())) {
+            const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
             let added = false;
             //  do nothing if link is dropped into any freeform view parent of dragged document
             const source =
                 !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
-                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
+                    ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' })
                     : Docs.Create.FontIconDocument({
                           title: 'anchor',
                           icon_label: '',
                           followLinkToggle: true,
                           icon: 'map-pin',
-                          x: xp,
-                          y: yp,
+                          x,
+                          y,
                           backgroundColor: '#ACCEF7',
                           layout_hideAllLinks: true,
                           layout_hideLinkButton: true,
@@ -441,14 +442,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        const [xp, yp] = this.screenToLocalXf.transformPoint(de.x, de.y);
-        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
-        else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
-        else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
+        if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
+        else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
+        else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
         return false;
     };
 
-    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToLocalXf.transformPoint(e.pageX, e.pageY));
+    onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToFreeformContentsXf.transformPoint(e.pageX, e.pageY));
 
     static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
         const doc2Layout = Doc.Layout(doc2);
@@ -489,7 +489,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
                 const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
                 de.moveDocument = this._props.moveDocument;
-                de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+                de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
                 DragManager.StartDocumentDrag(
                     clusterDocs.map(v => v.ContentDiv!),
                     de,
@@ -586,7 +586,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
-    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props'  used to be renderDepth + 1
         if (doc && this.childDocList?.includes(doc))
             switch (property) {
@@ -643,7 +643,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         break;
                     case InkTool.None:
                         if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
-                            this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
+                            this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY));
                             setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
                         }
                         break;
@@ -664,8 +664,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case GestureUtils.Gestures.Triangle:
             case GestureUtils.Gestures.Stroke:
                 const points = ge.points;
-                const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
-                const inkWidth = ActiveInkWidth() * this._props.ScreenToLocalTransform().Scale;
+                const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+                const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
                 const inkDoc = Docs.Create.InkDocument(
                     points,
                     { title: ge.gesture.toString(),
@@ -696,7 +696,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 break;
             case GestureUtils.Gestures.Text:
                 if (ge.text) {
-                    const B = this.screenToLocalXf.transformPoint(ge.points[0].X, ge.points[0].Y);
+                    const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y);
                     this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
                     e.stopPropagation();
                 }
@@ -719,7 +719,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 e.preventDefault();
             } else if (this.isContentActive() && e.shiftKey) {
                 // reset zoom of freeform view to 1-to-1 on a shift + double click
-                this.zoomSmoothlyAboutPt(this.screenToLocalXf.transformPoint(e.clientX, e.clientY), 1);
+                this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY), 1);
                 e.stopPropagation();
                 e.preventDefault();
             }
@@ -738,8 +738,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const shiftKey = e.shiftKey && !e.ctrlKey;
         PresBox.Instance?.pauseAutoPres();
         this._props.DocumentView?.().clearViewTransition();
-        const [dxi, dyi] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this._props.ScreenToLocalTransform().Rotate);
+        const [dxi, dyi] = this.screenToFreeformContentsXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+        const { x: dx, y: dy } = Utils.rotPt(dxi, dyi, this.ScreenToLocalBoxXf().Rotate);
         this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
@@ -940,7 +940,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return;
         let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
         if (deltaScale < 0) deltaScale = -deltaScale;
-        const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
+        const [x, y] = this.screenToFreeformContentsXf.transformPoint(pointX, pointY);
         const invTransform = this.panZoomXf.inverse();
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
@@ -982,7 +982,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this._props.isContentActive(true)) {
                     const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
                     const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
-                    this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
+                    this.scrollPan({ deltaX: -deltaX * this.screenToFreeformContentsXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToFreeformContentsXf.Scale });
                     break;
                 }
             default:
@@ -1040,9 +1040,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const minPanY = NumCast(this.dataDoc._freeform_panY_min, 0);
             const maxPanX = NumCast(this.dataDoc._freeform_panX_max, this.nativeWidth);
             const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
-            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this._props.ScreenToLocalTransform().Scale) / minScale;
+            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this._props.PanelWidth() - this._props.PanelHeight()) * this.ScreenToLocalBoxXf().Scale) / minScale;
             const nativeHeight = (this._props.PanelHeight() / this._props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
-            const maxScrollTop = this.nativeHeight / this._props.ScreenToLocalTransform().Scale - this._props.PanelHeight();
+            const maxScrollTop = this.nativeHeight / this.ScreenToLocalBoxXf().Scale - this._props.PanelHeight();
             const maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
                 scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
@@ -1116,11 +1116,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
         if (this.Document.isGroup) return;
         this.setPanZoomTransition(transitionTime);
-        const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
+        const screenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
-        const newScreenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
+        const newScreenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
-        const newpan = this.screenToLocalXf.transformDirection(scrDelta.x, scrDelta.y);
+        const newpan = this.screenToFreeformContentsXf.transformDirection(scrDelta.x, scrDelta.y);
         this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
         this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
     }
@@ -1229,7 +1229,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 onKey={this.onKeyDown}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 onBrowseClick={this.onBrowseClickHandler}
-                ScreenToLocalTransform={childLayout.z ? this._props.ScreenToLocalTransform : this.ScreenToLocalXf}
+                ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf}
                 PanelWidth={childLayout[Width]}
                 PanelHeight={childLayout[Height]}
                 childFilters={this.childDocFilters}
@@ -1264,7 +1264,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 return (
                     (this.addDocument?.(
                         (doc instanceof Doc ? [doc] : doc).map(doc => {
-                            const pt = this.screenToLocalXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
+                            const pt = this.screenToFreeformContentsXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
                             doc.x = pt[0];
                             doc.y = pt[1];
                             return doc;
@@ -1437,7 +1437,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @observable _hideInfo = false;
     @action closeInfo = () => (this._hideInfo = true);
-    infoUI = () => (this._hideInfo || this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
+    infoUI = () => (this._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />);
 
     componentDidMount() {
         this._props.setContentView?.(this);
@@ -1601,7 +1601,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     promoteCollection = () => {
         const childDocs = this.childDocs.slice();
         childDocs.forEach(doc => {
-            const scr = this.screenToLocalXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+            const scr = this.screenToFreeformContentsXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
@@ -1713,7 +1713,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         visited.add(this.Document);
         showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup));
         const activeDocs = this.getActiveDocuments();
-        const size = this.screenToLocalXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
+        const size = this.screenToFreeformContentsXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight());
         const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
         const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
         const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
@@ -1725,7 +1725,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const horizLines: number[] = [];
         const vertLines: number[] = [];
-        const invXf = this.screenToLocalXf.inverse();
+        const invXf = this.screenToFreeformContentsXf.inverse();
         snappableDocs
             .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
@@ -1822,8 +1822,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 selectDocuments={this.selectDocuments}
                 addDocument={this.addDocument}
                 addLiveTextDocument={this.addLiveTextBox}
-                getContainerTransform={this._props.ScreenToLocalTransform}
-                getTransform={this.ScreenToLocalXf}
+                getContainerTransform={this.ScreenToLocalBoxXf}
+                getTransform={this.ScreenToContentsXf}
                 panXFieldKey={this.panXFieldKey}
                 panYFieldKey={this.panYFieldKey}
                 isAnnotationOverlay={this.isAnnotationOverlay}>
@@ -1859,7 +1859,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30);
     lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30);
-    lightboxScreenToLocal = () => this._props.ScreenToLocalTransform().translate(-15, -15);
+    lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15);
     onPassiveWheel = (e: WheelEvent) => {
         const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight);
         const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling;
@@ -1950,7 +1950,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
                 }
                 while (parFfview?.Document.isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView;
                 const ffview = selfFfview && selfFfview.layoutDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
-                ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
+                ffview?.zoomSmoothlyAboutPt(ffview.screenToFreeformContentsXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
                 Doc.linkFollowHighlight(dv?.props.Document, false);
             }
         });
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 0f750c4f8..d078e1d4f 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -130,7 +130,7 @@ export class CollectionGridView extends CollectionSubView() {
      * Maps the x- and y- coordinates of the event to a grid cell.
      */
     screenToCell(sx: number, sy: number) {
-        const pt = this._props.ScreenToLocalTransform().transformPoint(sx, sy);
+        const pt = this.ScreenToLocalBoxXf().transformPoint(sx, sy);
         const x = Math.floor(pt[0] / this.colWidthPlusGap);
         const y = Math.floor((pt[1] + this._scroll) / this.rowHeight);
         return { x, y };
@@ -159,7 +159,7 @@ export class CollectionGridView extends CollectionSubView() {
         const xypos = this.flexGrid ? layout : this.unflexedPosition(this.renderedLayoutList.findIndex(l => l.i === layout.i));
         const pos = { x: xypos.x * this.colWidthPlusGap + this.margin, y: xypos.y * this.rowHeightPlusGap + this.margin - this._scroll };
 
-        return this._props.ScreenToLocalTransform().translate(-pos.x, -pos.y);
+        return this.ScreenToLocalBoxXf().translate(-pos.x, -pos.y);
     };
 
     /**
@@ -390,7 +390,7 @@ export class CollectionGridView extends CollectionSubView() {
                         numCols={this.numCols}
                         rowHeight={this.rowHeight}
                         setLayout={this.setLayout}
-                        transformScale={this._props.ScreenToLocalTransform().Scale}
+                        transformScale={this.ScreenToLocalBoxXf().Scale}
                         compactType={this.compaction} // determines whether nodes should remain in position, be bound to the top, or to the left
                         preventCollision={BoolCast(this.Document.gridPreventCollision)} // determines whether nodes should move out of the way (i.e. collide) when other nodes are dragged over them
                         margin={this.margin}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 5bea59e7b..563084af8 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -189,7 +189,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this._props.ScreenToLocalTransform().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+                return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
         }
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 3043eb0f8..bf0d39197 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -184,7 +184,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this._props.ScreenToLocalTransform().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
+                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerHeight;
         }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 2546f5b02..492aed0ea 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -16,7 +16,7 @@ import { undoable, undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
@@ -24,6 +24,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
+import { FieldViewProps } from '../../nodes/FieldView';
 
 export enum ColumnType {
     Number,
@@ -321,8 +322,8 @@ export class CollectionSchemaView extends CollectionSubView() {
                 change = this._displayColumnWidths[shrinking] - CollectionSchemaView._minColWidth;
             }
 
-            this._displayColumnWidths[shrinking] -= change * this._props.ScreenToLocalTransform().Scale;
-            this._displayColumnWidths[growing] += change * this._props.ScreenToLocalTransform().Scale;
+            this._displayColumnWidths[shrinking] -= change * this.ScreenToLocalBoxXf().Scale;
+            this._displayColumnWidths[growing] += change * this.ScreenToLocalBoxXf().Scale;
 
             return false;
         }
@@ -379,7 +380,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     highlightDropColumn = (e: PointerEvent) => {
         e.stopPropagation();
-        const mouseX = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+        const mouseX = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY)[0];
         const index = this.findDropIndex(mouseX);
         this._colEles.forEach((colRef, i) => {
             let leftStyle = '';
@@ -439,7 +440,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
-            const mouseX = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y)[0];
+            const mouseX = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y)[0];
             const index = this.findDropIndex(mouseX);
             this.moveColumn(de.complete.columnDragData.colIndex, index ?? de.complete.columnDragData.colIndex);
 
@@ -483,7 +484,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const nativeWidth = this._previewRef!.getBoundingClientRect();
         const minWidth = 40;
         const maxWidth = 1000;
-        const movedWidth = this._props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+        const movedWidth = this.ScreenToLocalBoxXf().transformDirection(nativeWidth.right - e.clientX, 0)[0];
         const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
         this.layoutDoc.schema_previewWidth = width;
         return false;
@@ -507,7 +508,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const found = this._tableContentRef && Array.from(this._tableContentRef.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]);
         if (found) {
             const rect = found.getBoundingClientRect();
-            const localRect = this._props.ScreenToLocalTransform().transformBounds(rect.left, rect.top, rect.width, rect.height);
+            const localRect = this.ScreenToLocalBoxXf().transformBounds(rect.left, rect.top, rect.width, rect.height);
             if (localRect.y < this.rowHeightFunc() || localRect.y + localRect.height > this._props.PanelHeight()) {
                 let focusSpeed = options.zoomTime ?? 50;
                 smoothScroll(focusSpeed, this._tableContentRef!, localRect.y + this._tableContentRef!.scrollTop - this.rowHeightFunc(), options.easeFunc);
@@ -829,7 +830,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     rowHeightFunc = () => (BoolCast(this.layoutDoc._schema_singleLine) ? CollectionSchemaView._rowSingleLineHeight : CollectionSchemaView._rowHeight);
     sortedDocsFunc = () => this.sortedDocs;
     isContentActive = () => this._props.isSelected() || this._props.isContentActive();
-    screenToLocal = () => this._props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
+    screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0);
     previewWidthFunc = () => this.previewWidth;
     render() {
         return (
@@ -960,8 +961,8 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
     }
 
     tableWidthFunc = () => this._props.schema.tableWidth;
-    screenToLocalXf = () => this._props.schema._props.ScreenToLocalTransform().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
-    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+    screenToLocalXf = () => this._props.schema.ScreenToLocalBoxXf().translate(0, -this._props.rowHeight() - this._props.index * this._props.rowHeight());
+    noOpacityStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (property === StyleProp.Opacity) return 1;
         return DefaultStyleProvider(doc, props, property);
     };
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 567cf193e..908cd5dc0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -460,7 +460,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @action
     timelineWhenChildContentsActiveChanged = (isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive));
 
-    timelineScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight);
+    timelineScreenToLocal = () => this.ScreenToLocalBoxXf().translate(0, -AudioBox.topControlsHeight);
 
     setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time);
 
@@ -564,7 +564,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             this._dropDisposer = DragManager.MakeDropTarget(
                 r,
                 (e, de) => {
-                    const [xp, yp] = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+                    const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
                     de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc,
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 324a4b8d1..ad5aabc21 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -15,7 +15,8 @@ import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { FieldViewProps } from './FieldView';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
     x: number;
@@ -148,7 +149,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return this._props.CollectionFreeFormView;
     }
 
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
         if (doc === this.layoutDoc) {
             switch (property) {
                 case StyleProp.Opacity:         return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
@@ -229,7 +230,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     @action public float = () => {
         const topDoc = this.Document;
         const containerDocView = this._props.docViewPath().lastElement();
-        const screenXf = containerDocView?.screenToNativeLocalTransform();
+        const screenXf = containerDocView?.screenToContentsTransform();
         if (screenXf) {
             SelectionManager.DeselectAll();
             if (topDoc.z) {
@@ -251,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     };
 
     nudge = (x: number, y: number) => {
-        const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+        const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y);
         this._props.Document.x = this._props.w_X() + locX;
         this._props.Document.y = this._props.w_Y() + locY;
     };
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index de382fca5..5e7e568b0 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -11,7 +11,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './ComparisonBox.scss';
-import { DocumentView, DocumentViewProps } from './DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
 
@@ -88,7 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     @action
     private onPointerMove = ({ movementX }: PointerEvent) => {
-        const width = movementX * this._props.ScreenToLocalTransform().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
+        const width = movementX * this.ScreenToLocalBoxXf().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
         if (width && width > 5 && width < this._props.PanelWidth()) {
             this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth();
         }
@@ -148,7 +148,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             e => this.clearDoc(which)
         );
     };
-    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (property === StyleProp.PointerEvents) return 'none';
         return this._props.styleProvider?.(doc, props, property);
     };
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 5365fe1b2..ed44d9269 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -80,7 +80,7 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
     };
 
     @computed get viewScale() {
-        return this._props.docView?.()?._props.ScreenToLocalTransform().Scale || 1;
+        return this._props.docView?.()?.screenToViewTransform().Scale || 1;
     }
     @computed get rowHeight() {
         console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 165057d21..c549a146a 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -257,7 +257,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
     }
 
     render() {
-        if (this._props.hideCount?.()) return null;
+        if (this.props.hideCount?.()) return null;
         const menuTitle = this._props.StartLink ? 'Drag or tap to start link' : 'Tap to complete link';
         const buttonTitle = 'Tap to view links; double tap to open link collection';
         const title = this._props.ShowCount ? buttonTitle : menuTitle;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7ec0382e4..7f1e547e4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -110,7 +110,7 @@ export interface DocFocusOptions {
     easeFunc?: 'linear' | 'ease'; // transition method for scrolling
 }
 export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
@@ -394,11 +394,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
             const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
             const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
-            const [left, top] = this._props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
-            dragData.offset = this._props
-                .ScreenToLocalTransform()
-                .scale(this.NativeDimScaling)
-                .transformDirection(x - left, y - top);
+            const screenXf = this.props.DocumentView().screenToViewTransform();
+            const [left, top] = screenXf.inverse().transformPoint(0, 0);
+            dragData.offset = screenXf.transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this._props.treeViewDoc;
             dragData.removeDocument = this._props.removeDocument;
@@ -881,7 +879,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this._props.PanelHeight() - this.headerMargin;
-    contentScreenToLocal = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+    screenToLocalContent = () => this.ScreenToLocalBoxXf().translate(0, -this.headerMargin);
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
@@ -925,7 +923,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     PanelHeight={this.panelHeight}
                     setHeight={this.setHeight}
                     isContentActive={this.isContentActive}
-                    ScreenToLocalTransform={this.contentScreenToLocal}
+                    ScreenToLocalTransform={this.screenToLocalContent}
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
                     setTitleFocus={this.setTitleFocus}
@@ -938,7 +936,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     anchorPanelWidth = () => this._props.PanelWidth() || 1;
     anchorPanelHeight = () => this._props.PanelHeight() || 1;
-    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+    anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.ShowTitle:     return '';
@@ -1073,7 +1071,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
     @observable _changingTitleField = false;
     @observable _dropDownInnerWidth = 0;
     fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
@@ -1431,11 +1429,20 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
     }
     @computed get hideLinkButton() {
-        return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HideLinkBtn + (this.IsSelected ? ':selected' : ''));
+        return (
+            this._props.hideLinkButton ||
+            this._props.renderDepth === -1 || //
+            (this.IsSelected && this._props.renderDepth) ||
+            !this._isHovering ||
+            (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
+            SnappingManager.IsDragging ||
+            SnappingManager.IsResizing
+        );
     }
-    hideLinkCount = () => false; // this._props.renderDepth === -1 || (this.IsSelected && this._props.renderDepth) || !this._isHovering || this.hideLinkButton;
+    hideLinkCount = () => (this.hideLinkButton ? true : false);
+
     @computed get linkCountView() {
-        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
+        return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
     }
     @computed get docViewPath(): DocumentView[] {
         return this._props.docViewPath ? [...this._props.docViewPath(), this] : [this];
@@ -1508,7 +1515,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         if (this.docView._componentView?.screenBounds?.()) {
             return this.docView._componentView.screenBounds();
         }
-        const xf = this.docView._props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
+        const xf = this.docView.ScreenToLocalBoxXf().scale(this.nativeScaling).inverse();
         const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
 
         if (this.docView._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
@@ -1566,7 +1573,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     };
 
     layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
-    scaleToScreenSpace = () => this._props.ScreenToLocalTransform().Scale;
+    screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
     docViewPathFunc = () => this.docViewPath;
     isSelected = () => this.IsSelected;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
@@ -1589,7 +1596,14 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
     PanelHeight = () => this.panelHeight;
     NativeDimScaling = () => this.nativeScaling;
     selfView = () => this;
-    screenToNativeLocalTransform = () =>
+    /**
+     * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
+     */
+    screenToViewTransform = () => this._props.ScreenToLocalTransform();
+    /**
+     * @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering)
+     */
+    screenToContentsTransform = () =>
         this._props
             .ScreenToLocalTransform()
             .translate(-this.centeringX, -this.centeringY)
@@ -1676,7 +1690,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
                             isSelected={this.isSelected}
                             select={this.select}
                             layout_fitWidth={this.layout_fitWidthFunc}
-                            ScreenToLocalTransform={this.screenToNativeLocalTransform}
+                            ScreenToLocalTransform={this.screenToContentsTransform}
                             focus={this._props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
                         />
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 28c614786..a5853499f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -90,7 +90,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this._disposers.sizer = reaction(
             () => ({
                 forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
-                scrSize: (this._props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
+                scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 1),
                 selected: this._props.isSelected(),
             }),
             ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'),
@@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @undoBatch
     setNativeSize = action(() => {
-        const scaling = (this._props.DocumentView?.()._props.ScreenToLocalTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+        const scaling = (this._props.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
         const nscale = NumCast(this._props.PanelWidth()) / scaling;
         const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
         this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
@@ -366,7 +366,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         TraceMobx();
         return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
     }
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this._props.ScreenToLocalTransform().Scale);
+    screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
     marqueeDown = (e: React.PointerEvent) => {
         if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 134f2e14a..7f1d41547 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -38,8 +38,8 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     screenBounds = () => {
         if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
-            const a_invXf = this.anchor1._props.ScreenToLocalTransform().inverse();
-            const b_invXf = this.anchor2._props.ScreenToLocalTransform().inverse();
+            const a_invXf = this.anchor1.screenToViewTransform().inverse();
+            const b_invXf = this.anchor2.screenToViewTransform().inverse();
             const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
             const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
 
@@ -67,9 +67,9 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     const b = (this.anchor2 ?? this.anchor1)!;
 
                     const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
-                    const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this._props.ScreenToLocalTransform();
-                    const a_invXf = a._props.ScreenToLocalTransform().inverse();
-                    const b_invXf = b._props.ScreenToLocalTransform().inverse();
+                    const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
+                    const a_invXf = a.screenToViewTransform().inverse();
+                    const b_invXf = b.screenToViewTransform().inverse();
                     const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
                     const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
                     const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index ffd52fb0e..41befbbfe 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -431,7 +431,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
@@ -531,7 +531,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude));
         const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
         const y = point.y + this._props.PanelHeight() / 2 + 32;
-        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -820,7 +820,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
@@ -1549,7 +1549,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             return null;
         }
         const scale = this._props.NativeDimScaling?.() || 1;
-        const parscale = scale === 1 ? 1 : this._props.ScreenToLocalTransform().Scale ?? 1;
+        const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1;
 
         const renderAnnotations = (childFilters?: () => string[]) => null;
         return (
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 722a347f1..9734d9db1 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -517,7 +517,7 @@
 
 //     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
 //     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-//     scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+//     scrollXf = () => this.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
 //     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
 //     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
 //     infoWidth = () => this._props.PanelWidth() / 5;
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index 2c31bbab7..8b22a1531 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -280,7 +280,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
 
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.OpaqueBackgroundFilter];
     infoWidth = () => this._props.PanelWidth() / 5;
@@ -401,7 +401,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
         const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude));
         const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2;
         const y = point.y + this._props.PanelHeight() / 2 + 32;
-        const cpt = this._props.ScreenToLocalTransform().inverse().transformPoint(x, y);
+        const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y);
         MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true);
 
         document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
@@ -677,7 +677,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
                 let target = document.elementFromPoint(e.x, e.y);
                 while (target) {
                     if (target === this._ref.current) {
-                        const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                        const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
                         const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
                         const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
                         const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 7f1d6b049..55a459a5c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -502,7 +502,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
     sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
-    sidebarScreenToLocal = () => this._props.ScreenToLocalTransform().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
+    sidebarScreenToLocal = () => this.ScreenToLocalBoxXf().translate((this.sidebarWidth() - this._props.PanelWidth()) / this.pdfScale, 0);
     @computed get sidebarCollection() {
         const renderComponent = (tag: string) => {
             const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 36527c311..f74e6fb2b 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -313,7 +313,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
                             addDocument={returnFalse}
-                            ScreenToLocalTransform={this._props.ScreenToLocalTransform}
                             renderDepth={this._props.renderDepth + 1}>
                             <>
                                 {this.threed}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7ab954b3f..ce73d9f37 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -682,7 +682,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             action(encodeURIComponent => {
                 this._clicking = false;
                 if (this._props.isContentActive()) {
-                    // const local = this._props.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
+                    // const local = this.ScreenToLocalTransform().scale(this._props.scaling?.() || 1).transformPoint(e.clientX, e.clientY);
                     // this.layoutDoc._layout_timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this._props.PanelHeight() * 100));
 
                     this.layoutDoc._layout_timelineHeightPercent = 80;
@@ -922,7 +922,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // renders video controls
     componentUI = (boundsLeft: number, boundsTop: number) => {
-        const xf = this._props.ScreenToLocalTransform().inverse();
+        const xf = this.ScreenToLocalBoxXf().inverse();
         const height = this._props.PanelHeight();
         const vidHeight = (height * this.heightPercent) / 100 / this.scaling();
         const vidWidth = this._props.PanelWidth() / this.scaling();
@@ -940,7 +940,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div
                     className="videoBox-ui"
                     style={{
-                        transform: `rotate(${this._props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${this.ScreenToLocalBoxXf().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a61837dad..3ad9f7634 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -33,7 +33,7 @@ import { Annotation } from '../pdf/Annotation';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
@@ -250,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
-            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToNativeLocalTransform().RotateDeg)}deg)`;
+            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -258,7 +258,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (rect && rect.width !== this._mainCont.current.clientWidth) {
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
-                    const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale;
+                    const scale = this._url ? 1 : this.ScreenToLocalBoxXf().Scale;
                     // transforms the positions from screen onto the pdf div
                     annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
                     annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
@@ -771,7 +771,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable lighttext = false;
 
     @computed get urlContent() {
-        if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>;
+        if (this.ScreenToLocalBoxXf().Scale > 25) return <div></div>;
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -1061,11 +1061,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
-    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8bf8abafa..66802d198 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,7 +1,6 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { isEqual } from 'lodash';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
@@ -21,12 +20,10 @@ import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from '../../../../fields/RichTextField';
-import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -46,7 +43,6 @@ import { CollectionTreeView } from '../../collections/CollectionTreeView';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { DocumentButtonBar } from '../../DocumentButtonBar';
 import { Colors } from '../../global/globalEnums';
 import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -71,9 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
 import { RichTextRules } from './RichTextRules';
 import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
-export const GoogleRef = 'googleDocId';
 // import * as applyDevTools from 'prosemirror-dev-tools';
-type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
 
 export interface FormattedTextBoxProps {}
 @observer
@@ -1189,15 +1183,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._cachedLinks = newLinks;
             }
         );
-        this._disposers.buttonBar = reaction(
-            () => DocumentButtonBar.Instance,
-            instance => {
-                if (instance) {
-                    this.pullFromGoogleDoc(this.checkState);
-                    this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true));
-                }
-            }
-        );
         this._disposers.editorState = reaction(
             () => {
                 const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc;
@@ -1218,24 +1203,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
             }
         );
-        this._disposers.pullDoc = reaction(
-            () => this._props.Document[Pulls],
-            () => {
-                if (!DocumentButtonBar.hasPulledHack) {
-                    DocumentButtonBar.hasPulledHack = true;
-                    this.pullFromGoogleDoc(this.dataDoc.googleDocUnchanged ? this.checkState : this.updateState);
-                }
-            }
-        );
-        this._disposers.pushDoc = reaction(
-            () => this._props.Document[Pushes],
-            () => {
-                if (!DocumentButtonBar.hasPushedHack) {
-                    DocumentButtonBar.hasPushedHack = true;
-                    this.pushToGoogleDoc();
-                }
-            }
-        );
 
         this._disposers.search = reaction(
             () => Doc.IsSearchMatch(this.Document),
@@ -1295,80 +1262,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         setTimeout(this.tryUpdateScrollHeight, 250);
     }
 
-    pushToGoogleDoc = async () => {
-        this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-            const modes = GoogleApiClientUtils.Docs.WriteMode;
-            let mode = modes.Replace;
-            let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], 'string');
-            if (!reference) {
-                mode = modes.Insert;
-                reference = { title: StrCast(this.dataDoc.title) };
-            }
-            const redo = async () => {
-                if (this._editorView && reference) {
-                    const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
-                    const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                    response?.documentId && (this.dataDoc[GoogleRef] = response.documentId);
-                    const pushSuccess = response !== undefined && !('errors' in response);
-                    dataDoc.googleDocUnchanged = pushSuccess;
-                    DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
-                }
-            };
-            const undo = () => {
-                if (exportState && reference) {
-                    const content: GoogleApiClientUtils.Docs.Content = {
-                        text: exportState.text,
-                        requests: [],
-                    };
-                    GoogleApiClientUtils.Docs.write({ reference, content, mode });
-                }
-            };
-            UndoManager.AddEvent({ undo, redo, prop: '' });
-            redo();
-        });
-    };
-
-    pullFromGoogleDoc = async (handler: PullHandler) => {
-        const dataDoc = this.dataDoc;
-        const documentId = StrCast(dataDoc[GoogleRef]);
-        let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
-        if (documentId) {
-            exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
-        }
-        exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
-    };
-
-    updateState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        let pullSuccess = false;
-        if (exportState !== undefined) {
-            pullSuccess = true;
-            dataDoc[this.fieldKey] = new RichTextField(JSON.stringify(exportState.state.toJSON()));
-            setTimeout(() => {
-                if (this._editorView) {
-                    const state = this._editorView.state;
-                    const end = state.doc.content.size - 1;
-                    this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
-                }
-            }, 0);
-            dataDoc.title = exportState.title;
-            this.dataDoc.title_custom = true;
-            dataDoc.googleDocUnchanged = true;
-        } else {
-            delete dataDoc[GoogleRef];
-        }
-        DocumentButtonBar.Instance.startPullOutcome(pullSuccess);
-    };
-
-    checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
-        if (exportState && this._editorView) {
-            const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
-            const equalTitles = dataDoc.title === exportState.title;
-            const unchanged = equalContent && equalTitles;
-            dataDoc.googleDocUnchanged = unchanged;
-            DocumentButtonBar.Instance.setPullState(unchanged);
-        }
-    };
-
     clipboardTextSerializer = (slice: Slice): string => {
         let text = '',
             separated = true;
@@ -1462,7 +1355,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined;
                     if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) {
                         const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE);
-                        const scrollPos = scrollRef.scrollTop + shift * self._props.ScreenToLocalTransform().Scale;
+                        const scrollPos = scrollRef.scrollTop + shift * self.ScreenToLocalBoxXf().Scale;
                         if (this._focusSpeed !== undefined) {
                             scrollPos && (this._scrollStopper = smoothScroll(this._focusSpeed, scrollRef, scrollPos, 'ease', this._scrollStopper));
                         } else {
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 1a92acea1..7d0086c0c 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -13,7 +13,7 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(ImportElementBox, fieldKey);
     }
 
-    screenToLocalXf = () => this._props.ScreenToLocalTransform().scale(1 * (this._props.NativeDimScaling?.() || 1));
+    screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1));
     @computed get mainItem() {
         return (
             <div style={{ backgroundColor: 'pink' }}>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0305689e7..6fa64a765 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -979,7 +979,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     enterMinimize = () => {
         this.updateCurrentPresentation(this.Document);
         clearTimeout(this._presTimer);
-        const pt = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+        const pt = this.ScreenToLocalBoxXf().inverse().transformPoint(0, 0);
         this._props.removeDocument?.(this.layoutDoc);
         return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
     };
@@ -1076,7 +1076,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
     removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
-    getTransform = () => this._props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
+    getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
     panelHeight = () => this._props.PanelHeight() - 40;
     /**
      * For sorting the array so that the order is maintained when it is dropped.
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index ec5d090dd..4945d66c8 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -19,7 +19,7 @@ import { TreeView } from '../../collections/TreeView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
-import { DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../../nodes/FieldView';
 import { StyleProp } from '../../StyleProvider';
 import { PresBox } from './PresBox';
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
     embedHeight = () => this.collapsedHeight + this.expandViewHeight;
     embedWidth = () => this._props.PanelWidth() / 2;
-    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
     };
     /**
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f41094010..4a7e35c16 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -17,11 +17,11 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewInternalProps, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import { ObservableReactComponent } from '../ObservableReactComponent';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp, testDocProps } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
@@ -53,7 +53,7 @@ interface IViewerProps extends FieldViewProps {
 export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
 
-    constructor(props: any) {
+    constructor(props: IViewerProps) {
         super(props);
         makeObservable(this);
     }
@@ -419,7 +419,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     @action
     createTextAnnotation = (sel: Selection, selRange: Range) => {
         if (this._mainCont.current) {
-            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToNativeLocalTransform().RotateDeg)}deg)`;
+            this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
             const boundingRect = this._mainCont.current.getBoundingClientRect();
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
@@ -491,16 +491,17 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     }
 
     getScrollHeight = () => this._scrollHeight;
-    scrollXf = () => (this._mainCont.current ? this._props.ScreenToLocalTransform().translate(0, NumCast(this._props.layoutDoc._layout_scrollTop)) : this._props.ScreenToLocalTransform());
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, this._mainCont.current ? NumCast(this._props.layoutDoc._layout_scrollTop) : 0);
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this._props.layoutDoc._freeform_scale, 1));
     panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1);
     panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
     opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed && this._props.isContentActive() ? [] : [Utils.OpaqueBackgroundFilter])];
-    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
+    childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
+            const docProps = testDocProps(props) ? props : undefined;
             if (this.inlineTextAnnotations.includes(doc) || this._props.isContentActive() === false) return 'none';
-            const isInk = doc.layout_isSvg && !props?.LayoutTemplateString;
+            const isInk = doc.layout_isSvg && !docProps?.LayoutTemplateString;
             return isInk ? 'visiblePainted' : 'all';
         }
         return this._props.styleProvider?.(doc, props, property);
-- 
cgit v1.2.3-70-g09d2

