From 2b0e4ccc096998eb1d727f2e85ea8c1a63b27e08 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 18 Nov 2023 23:47:13 -0500
Subject: fixed ctrl-drag for expressions, maps, fform doc selections. fixed
 using shift to add Doc to a selection and also when bounding box already
 covers the doc to add.  fixed dragging maximize button to start goldenlayout
 drag properly.  fixed typing character to group,etc a multiselection when a
 text doc has input focus.  fixed using clusters. add Shift-U to ungroup
 alternate group style.  multi-select blurs() all active inputs.
 shift-selecting a multi-selected Doc, deselects it.

---
 src/client/views/collections/collectionGrid/CollectionGridView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

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();
         }
     };
 
-- 
cgit v1.2.3-70-g09d2


From ba3b3f6f261074bd3f35012bde8730f5d4a36905 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Nov 2023 10:02:34 -0500
Subject: numerous changes to fix bugs and to fix/remove old or hacky code. 
 fixed doc dec resizing.  moving this.rootDoc => this.Document .  fixing
 template artifacts.

---
 src/client/documents/Documents.ts                  |  19 +-
 src/client/util/CurrentUserUtils.ts                |  96 +--
 src/client/util/Scripting.ts                       |   4 +-
 src/client/views/DashboardView.tsx                 |   2 +-
 src/client/views/DocComponent.tsx                  |  16 +-
 src/client/views/DocumentButtonBar.tsx             |  14 +-
 src/client/views/DocumentDecorations.tsx           |  18 +-
 src/client/views/GlobalKeyHandler.ts               |   4 +-
 src/client/views/MainView.tsx                      |   6 +-
 src/client/views/MarqueeAnnotator.tsx              |  26 +-
 src/client/views/PropertiesButtons.tsx             |   5 +-
 src/client/views/PropertiesView.tsx                |  47 +-
 src/client/views/StyleProvider.tsx                 |   9 +-
 .../views/collections/CollectionDockingView.tsx    |  53 +-
 src/client/views/collections/CollectionMenu.tsx    | 824 +--------------------
 .../views/collections/CollectionNoteTakingView.tsx |   4 +-
 .../views/collections/CollectionPileView.tsx       |  16 +-
 .../collections/CollectionStackedTimeline.tsx      |  16 +-
 .../views/collections/CollectionStackingView.scss  |   2 +-
 .../views/collections/CollectionStackingView.tsx   |  96 ++-
 src/client/views/collections/CollectionSubView.tsx |  20 +-
 .../views/collections/CollectionTimeView.tsx       |   4 +-
 .../views/collections/CollectionTreeView.tsx       |  16 +-
 src/client/views/collections/TabDocView.tsx        |   4 +-
 src/client/views/collections/TreeView.tsx          |  13 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   1 +
 .../CollectionFreeFormPannableContents.tsx         |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 199 ++---
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 .../collectionGrid/CollectionGridView.tsx          |   5 +-
 .../collectionLinear/CollectionLinearView.tsx      |   8 +-
 .../CollectionMulticolumnView.scss                 |   2 +-
 .../CollectionMulticolumnView.tsx                  |  39 +-
 .../CollectionMultirowView.scss                    |   5 -
 .../CollectionMultirowView.tsx                     |  37 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  10 +-
 .../collectionSchema/SchemaTableCell.tsx           |   3 +-
 src/client/views/global/globalScripts.ts           |   8 +-
 src/client/views/nodes/AudioBox.tsx                |   4 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  51 +-
 src/client/views/nodes/DocumentContentsView.tsx    |  23 +-
 src/client/views/nodes/DocumentView.tsx            | 171 +++--
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 121 ++-
 src/client/views/nodes/FunctionPlotBox.tsx         |   6 +-
 src/client/views/nodes/ImageBox.tsx                |   3 +-
 src/client/views/nodes/KeyValueBox.tsx             |   2 +-
 src/client/views/nodes/LinkBox.tsx                 |  18 +-
 src/client/views/nodes/MapBox/MapPushpinBox.tsx    |   6 +-
 src/client/views/nodes/PDFBox.tsx                  |   1 -
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  12 +-
 src/client/views/nodes/ScreenshotBox.tsx           |   4 +-
 src/client/views/nodes/ScriptingBox.tsx            |   6 +-
 src/client/views/nodes/VideoBox.tsx                |   6 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  53 +-
 .../views/nodes/formattedText/RichTextMenu.tsx     |  46 +-
 .../views/nodes/formattedText/RichTextRules.ts     |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |  97 ++-
 src/client/views/nodes/trails/PresElementBox.tsx   |  64 +-
 src/client/views/pdf/PDFViewer.tsx                 |   5 +-
 src/client/views/search/SearchBox.tsx              |   2 +-
 src/fields/Doc.ts                                  |  15 +-
 src/fields/ScriptField.ts                          |  26 +-
 63 files changed, 798 insertions(+), 1605 deletions(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 029653204..4def19384 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -292,6 +292,7 @@ export class DocumentOptions {
     childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view)
     childLayoutString?: string; // template string for collection to use to render its children
     childDocumentsActive?: BOOLt = new BoolInfo('whether child documents are active when parent is document active');
+    childLayoutFitWidth?: BOOLt = new BoolInfo("whether a child doc's fitWith should be overriden by collection");
     childDontRegisterViews?: BOOLt = new BoolInfo('whether child document views should be registered so that they can be found when following links, etc');
     childHideLinkButton?: BOOLt = new BoolInfo('hide link buttons on all children');
     childContextMenuFilters?: List<ScriptField>;
@@ -1134,7 +1135,7 @@ export namespace Docs {
         // }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _type_collection: CollectionViewType.Freeform }, id);
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id);
             documents.forEach(d => Doc.SetContainer(d, inst));
             return inst;
         }
@@ -1217,7 +1218,7 @@ export namespace Docs {
         }
 
         export function FunctionPlotDocument(documents: Array<Doc>, options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.FUNCPLOT), new List(documents), { ...(options || {}) });
+            return InstanceFromProto(Prototypes.get(DocumentType.FUNCPLOT), new List(documents), { title: 'func plot', ...(options || {}) });
         }
 
         export function ButtonDocument(options?: DocumentOptions) {
@@ -1487,7 +1488,7 @@ export namespace DocUtils {
             Object.keys(scripts).map(key => {
                 const script = scripts[key];
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
-                    doc[key] = ScriptField.MakeScript(script, {
+                    (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, {
                         self: Doc.name,
                         this: Doc.name,
                         dragData: DragManager.DocumentDragData.name,
@@ -1511,7 +1512,7 @@ export namespace DocUtils {
                     const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
                     if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) {
                         const setFunc = Cast(funcs[key + '-setter'], 'string', null);
-                        doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
+                        (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined;
                     }
                 });
         return doc;
@@ -1717,7 +1718,7 @@ export namespace DocUtils {
     }
     export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) {
         const templateName = templateSignature.replace(/\(.*\)/, '');
-        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
+        docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature);
 
         const customName = 'layout_' + templateSignature;
         const _width = NumCast(doc._width);
@@ -1987,9 +1988,9 @@ ScriptingGlobals.add(function makeDelegate(proto: any) {
     const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
     return d;
 });
-ScriptingGlobals.add(function generateLinkTitle(self: Doc) {
-    const link_anchor_1title = self.link_anchor_1 && self.link_anchor_1 !== self ? Cast(self.link_anchor_1, Doc, null)?.title : '<?>';
-    const link_anchor_2title = self.link_anchor_2 && self.link_anchor_2 !== self ? Cast(self.link_anchor_2, Doc, null)?.title : '<?>';
-    const relation = self.link_relationship || 'to';
+ScriptingGlobals.add(function generateLinkTitle(link: Doc) {
+    const link_anchor_1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>';
+    const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>';
+    const relation = link.link_relationship || 'to';
     return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`;
 });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 18c7a1fb6..2f2ac2a71 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -284,7 +284,7 @@ export class CurrentUserUtils {
                                                                                                 treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, 
                                                                                                 dropAction:'move', treeView_Type: TreeViewType.outline, 
                                                                                                 backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true 
-                                                                                            }, funcs: {title: 'self.text?.Text'}},
+                                                                                            }, funcs: {title: 'this.text?.Text'}},
             ];
 
         emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
@@ -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(this.target?.data).filter(doc => !docList(this.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()"} },
@@ -467,6 +467,7 @@ export class CurrentUserUtils {
         const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
         const reqdToolOps:DocumentOptions = {
             title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
+            layout_explainer: "This is a palette of documents that can be created.",
             _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, 
         };
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
@@ -558,7 +559,7 @@ export class CurrentUserUtils {
         const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, 
             title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, color: Colors.BLACK, buttonText: "Empty", icon: "trash", isSystem: true,
             toolTip: "Empty recently closed",};
-        DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
+        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"))) {
             recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
@@ -628,42 +629,42 @@ export class CurrentUserUtils {
     }
     static stackTools(): Button[] {
         return [
-            { title: "Center",    icon: "align-center",        toolTip: "Center Align Stack",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center",     funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Center",    icon: "align-center",        toolTip: "Center Align Stack",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center",     funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
     static viewTools(): Button[] {
         return [
-            { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "Grid",      icon: "border-all",          toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "View All",  icon: "object-group",        toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Grid",      icon: "border-all",          toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "View All",  icon: "object-group",        toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
-            { title: "Fit All",   icon: "arrows-left-right",   toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "Clusters",  icon: "braille",             toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "Cards",     icon: "brain",               toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "Arrange",   icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Fit All",   icon: "arrows-left-right",   toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Clusters",  icon: "braille",             toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Cards",     icon: "brain",               toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            { title: "Arrange",   icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
     static textTools():Button[] {
         return [
-            { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
+            { title: "Font",     toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            { title: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
-            { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
-            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
-            { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
-            { 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: "Font Size",toolTip: "Font size (%size)",    btnType: ButtonType.NumberDropdownButton,              toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
+            { title: "Color",    toolTip: "Font color (%color)",  btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
+            { title: "Highlight",toolTip: "Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Bold",     toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",     ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Italic",   toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Under",    toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Bullets",  toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",   ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "#",        toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",  ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+            { title: "Vcenter",  toolTip: "Vertical center",      btnType: ButtonType.ToggleButton, icon: "pallet",      toolType:"vcent",    ignoreClick: true,   scripts: {onClick: '{ return toggleCharStyle(this.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: "Left",     toolTip: "Left align (Cmd-[)",   btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",  ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}' }},
+                { title: "Center",   toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
+                { title: "Right",    toolTip: "Right align (Cmd-])",  btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.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: "Dictate",  toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation", ignoreClick: true,  scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}},
+            { title: "NoLink",   toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
             // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
             // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
@@ -672,15 +673,15 @@ export class CurrentUserUtils {
 
     static inkTools():Button[] {
         return [
-            { title: "Pen",    toolTip: "Pen (Ctrl+P)",                     btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                            scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
-            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
-            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",                  btnType: ButtonType.ToggleButton, icon: "eraser",     toolType: "eraser",                         scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
-            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType:GestureUtils.Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
-            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
-            { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnMin: 1},
-            { title: "Ink",    toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
+            { title: "Pen",    toolTip: "Pen (Ctrl+P)",                     btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                            scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
+            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
+            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",                  btnType: ButtonType.ToggleButton, icon: "eraser",     toolType: "eraser",                         scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType:GestureUtils.Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
+            { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberSliderButton,               toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
+            { title: "Ink",    toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} },
         ];
     }
 
@@ -709,17 +710,17 @@ export class CurrentUserUtils {
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
             { title: "Fill",    icon: "fill-drip",     toolTip: "Fill/Background Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
-            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
-            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
-            { title: "Num",     icon:"",               toolTip: "Frame Number (click to toggle edit mode)",    btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
-            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
-            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
-            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)'},       scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
+            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
            ];
     }
 
@@ -904,7 +905,7 @@ export class CurrentUserUtils {
         this.setupDocTemplates(doc); // sets up the template menu of templates
         //this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
         DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) =>  Docs.Prototypes.MainScriptDocument(), {});
-        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+        DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
         
         Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
         Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
@@ -976,6 +977,7 @@ export class CurrentUserUtils {
                     DashboardView.createNewDashboard(undefined, "guest dashboard");
                 } else {
                     userDoc.activePage = "home";
+                    userDoc.noviceMode = true;
                 }
             }
             return userDoc;
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 70c2e3842..400b63a1c 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -264,6 +264,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
 }
 
 ScriptingGlobals.add(CompileScript);
-ScriptingGlobals.add(function runScript(self: Doc, script: ScriptField) {
-    return script?.script.run({ this: self, self: self }).result;
+ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
+    return script?.script.run({ this: doc }).result;
 });
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 9e2ed7822..2765e95e6 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -370,7 +370,7 @@ export class DashboardView extends React.Component {
         const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
         const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row');
 
-        Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc);
+        Doc.AddDocToList(Doc.MyHeaderBar, 'data', freeformDoc, undefined, undefined, true);
         dashboardDoc['pane-count'] = 1;
         freeformDoc.embedContainer = dashboardDoc;
         dashboardDoc.myOverlayDocs = new List<Doc>();
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index fdaca8056..61b7a3bff 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -10,6 +10,8 @@ import { DocUtils } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentView } from './nodes/DocumentView';
 import * as React from 'react';
+import { DocumentManager } from '../util/DocumentManager';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 export interface DocComponentProps {
@@ -26,7 +28,7 @@ export function DocComponent<P extends DocComponentProps>() {
         }
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() {
-            return Cast(this.props.Document.rootDocument, Doc, null) || 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() {
@@ -58,7 +60,9 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
     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); }
-
+        @computed get Document() {
+            return this.props.Document;
+        }
         // This is the "The Document" -- it encapsulates, data, layout, and any templates
         @computed get rootDoc() {
             return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document;
@@ -146,7 +150,11 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                         doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
                     }
                 });
-                this.isAnyChildContentActive() && this.props.select(false);
+                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);
+                }
                 return true;
             }
 
@@ -173,7 +181,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) {
                 return false;
             }
-            const targetDataDoc = this.rootDoc[DocData];
+            const targetDataDoc = this.dataDoc;
             const effectiveAcl = GetEffectiveAcl(targetDataDoc);
 
             if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c1ec5b4a4..bd82f7782 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -574,12 +574,12 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         e.stopPropagation();
     };
     render() {
-        if (!this.view0) return null;
-
-        const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField;
         const doc = this.view0?.props.Document;
-        const considerPull = isText && this.considerGoogleDocsPull;
-        const considerPush = isText && this.considerGoogleDocsPush;
+        if (!doc || !this.view0) return null;
+
+        const isText = () => doc[this.view0!.LayoutFieldKey] instanceof RichTextField;
+        const considerPull = () => isText() && this.considerGoogleDocsPull;
+        const considerPush = () => isText() && this.considerGoogleDocsPush;
         return (
             <div className="documentButtonBar">
                 <div className="documentButtonBar-button">
@@ -608,11 +608,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
-                    <div className="documentButtonBar-button" style={{ display: !considerPush ? 'none' : '' }}>
+                    <div className="documentButtonBar-button" style={{ display: !considerPush() ? 'none' : '' }}>
                         {this.considerGoogleDocsPush}
                     </div>
                 )}
-                <div className="documentButtonBar-button" style={{ display: !considerPull ? 'none' : '' }}>
+                <div className="documentButtonBar-button" style={{ display: !considerPull() ? 'none' : '' }}>
                     {this.considerGoogleDocsPull}
                 </div>
                 <div className="documentButtonBar-button">{this.menuButton}</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d4b474de9..4ede2e2bb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -6,7 +6,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 } from '../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit } from '../../fields/DocSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
@@ -71,10 +71,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                           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)));
+                    (this.Bounds.x > center.x+x || this.Bounds.r < center.x+x || 
+                     this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
             })); // prettier-ignore
     }
 
@@ -551,8 +549,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 if (setData) Doc.SetNativeHeight(Doc.GetProto(doc), NumCast(doc._nativeHeight));
             }
 
-            doc._width = NumCast(doc._width) * scale.x;
-            doc._height = NumCast(doc._height) * scale.y;
+            doc._width = Math.max(1, NumCast(doc._width) * scale.x);
+            doc._height = Math.max(1, 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;
@@ -675,7 +673,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             seldocview.props.hideDeleteButton ||
             seldocview.rootDoc.hideDeleteButton ||
             SelectionManager.Views().some(docView => {
-                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().rootDoc[DocData]) : AclEdit;
+                const collectionAcl = docView.props.docViewPath()?.lastElement() ? GetEffectiveAcl(docView.props.docViewPath().lastElement().dataDoc) : AclEdit;
                 return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin;
             });
         const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
@@ -801,11 +799,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     <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')}
                                 </>
                             )}
+                            {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/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 87f81fe76..cc9039ed1 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,5 +1,5 @@
 import { random } from 'lodash';
-import { action, runInAction } from 'mobx';
+import { action } from 'mobx';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
@@ -15,7 +15,6 @@ import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
-import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
 import { ContextMenu } from './ContextMenu';
 import { DocumentDecorations } from './DocumentDecorations';
@@ -146,7 +145,6 @@ export class KeyManager {
                 SharingManager.Instance.close();
                 if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
                 GroupManager.Instance.close();
-                CollectionFreeFormViewChrome.Instance?.clearKeepPrimitiveMode();
                 window.getSelection()?.empty();
                 document.body.focus();
                 break;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f42c17694..ac0cc3f8c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -937,14 +937,14 @@ export class MainView extends React.Component {
         SnappingManager.GetIsResizing();
         const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs().lastElement();
         const dragPar = dragged ? DocumentManager.Instance.getDocumentView(dragged)?.CollectionFreeFormView : undefined;
-        return !dragPar?.rootDoc.freeform_snapLines ? null : (
+        return !dragPar?.layoutDoc.freeform_snapLines ? null : (
             <div className="mainView-snapLines">
                 <svg style={{ width: '100%', height: '100%' }}>
                     {SnappingManager.horizSnapLines().map((l, i) => (
-                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
+                        <line key={i} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                     {SnappingManager.vertSnapLines().map((l, i) => (
-                        <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.rootDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
+                        <line key={i} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(dragPar.layoutDoc.backgroundColor ?? 'gray')} opacity={0.3} strokeWidth={1} strokeDasharray={'2 2'} />
                     ))}
                 </svg>
             </div>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 20c7a08fa..8b8838464 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, ObservableMap, runInAction, trace } from 'mobx';
+import { action, computed, observable, ObservableMap } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols';
@@ -19,7 +19,7 @@ import React = require('react');
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
-    rootDoc: Doc;
+    Document: Doc;
     down?: number[];
     scrollTop: number;
     scaling?: () => number;
@@ -69,8 +69,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             const marqueeAnno = Docs.Create.FreeformDocument([], {
                 onClick: isLinkButton ? FollowLinkScript() : undefined,
                 backgroundColor: color,
-                annotationOn: this.props.rootDoc,
-                title: 'Annotation on ' + this.props.rootDoc.title,
+                annotationOn: this.props.Document,
+                title: 'Annotation on ' + this.props.Document.title,
             });
             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;
@@ -82,13 +82,13 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         }
 
         const textRegionAnno = Docs.Create.HTMLMarkerDocument([], {
-            annotationOn: this.props.rootDoc,
+            annotationOn: this.props.Document,
             text: this.props.selectionText(),
             backgroundColor: 'transparent',
             presentation_duration: 2100,
             presentation_transition: 500,
             presentation_zoomText: true,
-            title: 'Selection on ' + this.props.rootDoc.title,
+            title: 'Selection on ' + this.props.Document.title,
         });
         let minX = Number.MAX_VALUE;
         let maxX = -Number.MAX_VALUE;
@@ -126,7 +126,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
     @action
     highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean, summarize?: boolean) => {
         // creates annotation documents for current highlights
-        const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]);
+        const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
         const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations);
         addAsAnnotation && annotationDoc && this.props.addDocument(annotationDoc);
         return annotationDoc as Doc;
@@ -146,11 +146,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         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);
+        const scalex = this.props.mainCont.offsetWidth / NumCast(this.props.Document.width);
+        const scaley = this.props.mainCont.offsetHeight / NumCast(this.props.Document.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
+        return { x: scalex * (downPt.x + NumCast(this.props.Document.width) / scale / 2) * scale, 
+                 y: scaley * (downPt.y + NumCast(this.props.Document.height) / scale / 2) * scale + this.props.annotationLayerScrollTop }; // prettier-ignore
     };
 
     @action
@@ -184,14 +184,14 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             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');
+                const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.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.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.Document;
                         e.annoDragData.linkSourceDoc.followLinkZoom = false;
                     }
                 },
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 18f53b8e7..84b1bf038 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -42,12 +42,15 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @computed get selectedDoc() {
         return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc;
     }
+    @computed get selectedLayoutDoc() {
+        return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.layoutDoc;
+    }
     @computed get selectedTabView() {
         return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost;
     }
 
     propertyToggleBtn = (label: (on?: any) => string, property: string, tooltip: (on?: any) => string, icon: (on?: any) => any, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => {
-        const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc;
+        const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedLayoutDoc;
         const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true);
         return !targetDoc ? null : (
             <Toggle
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1b9b26111..8f37a0564 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -63,7 +63,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     }
 
     @computed get selectedDoc() {
-        return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard;
+        return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
+    }
+
+    @computed get selectedLayoutDoc() {
+        return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -126,13 +130,13 @@ 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(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);
+    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
     docWidth = () => {
-        if (this.selectedDoc) {
-            const layoutDoc = this.selectedDoc;
+        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;
@@ -142,8 +146,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @action
     docHeight = () => {
-        if (this.selectedDoc && this.dataDoc) {
-            const layoutDoc = this.selectedDoc;
+        const layoutDoc = this.selectedLayoutDoc;
+        if (layoutDoc && this.dataDoc) {
             return Math.max(
                 70,
                 Math.min(
@@ -288,7 +292,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             return (
                 <div ref={this.propertiesDocViewRef} style={{ pointerEvents: 'none', display: 'inline-block', height: panelHeight() }} key={this.selectedDoc[Id]}>
                     <DocumentView
-                        Document={layoutDoc}
+                        Document={this.selectedDoc}
                         DataDoc={this.dataDoc}
                         renderDepth={1}
                         fitContentsToBox={returnTrue}
@@ -328,7 +332,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
      */
     @undoBatch
     changePermissions = (e: any, user: string) => {
-        const docs = (SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => dv.props.Document)).filter(doc => doc).map(doc => (this.layoutDocAcls ? doc! : DocCast(doc)[DocData]));
+        const docs = SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc));
         SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls);
     };
 
@@ -453,7 +457,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         const individualTableEntries = [];
         const usersAdded: string[] = []; // all shared users being added - organized by denormalized email
 
-        const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc);
+        const seldoc = this.layoutDocAcls ? this.selectedLayoutDoc : this.selectedDoc?.[DocData];
         // adds each user to usersAdded
         SharingManager.Instance.users.forEach(eachUser => {
             var userOnDoc = true;
@@ -630,10 +634,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @action
     rotate = (angle: number) => {
         const _centerPoints: { X: number; Y: number }[] = [];
-        if (this.selectedDoc) {
-            const doc = this.selectedDoc;
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                const ink = Cast(doc.data, InkField)?.inkData;
+        const doc = this.selectedDoc;
+        const layout = this.selectedLayoutDoc;
+        if (doc && layout) {
+            if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
+                const ink = Cast(doc.stroke, InkField)?.inkData;
                 if (ink) {
                     const xs = ink.map(p => p.X);
                     const ys = ink.map(p => p.Y);
@@ -646,9 +651,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             }
 
             var index = 0;
-            if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
-                doc.rotation = NumCast(doc.rotation) + angle;
-                const inks = Cast(doc.data, InkField)?.inkData;
+            if (doc.type === DocumentType.INK && doc.x && doc.y && layout._width && layout._height && doc.data) {
+                layout.rotation = NumCast(layout.rotation) + angle;
+                const inks = Cast(doc.stroke, InkField)?.inkData;
                 if (inks) {
                     const newPoints: { X: number; Y: number }[] = [];
                     inks.forEach(ink => {
@@ -656,7 +661,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
                         newPoints.push({ X: newX, Y: newY });
                     });
-                    doc.data = new InkField(newPoints);
+                    doc.stroke = new InkField(newPoints);
                     const xs = newPoints.map(p => p.X);
                     const ys = newPoints.map(p => p.Y);
                     const left = Math.min(...xs);
@@ -664,8 +669,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     const right = Math.max(...xs);
                     const bottom = Math.max(...ys);
 
-                    doc._height = bottom - top;
-                    doc._width = right - left;
+                    layout._height = bottom - top;
+                    layout._width = right - left;
                 }
                 index++;
             }
@@ -1178,7 +1183,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         return (
             <>
                 <PropertiesSection title="Appearance" isOpen={this.openAppearance} setIsOpen={bool => (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}>
-                    {this.selectedDoc?.layout_isSvg ? this.appearanceEditor : null}
+                    {this.selectedLayoutDoc?.layout_isSvg ? this.appearanceEditor : null}
                 </PropertiesSection>
                 <PropertiesSection title="Transform" isOpen={this.openTransform} setIsOpen={bool => (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}>
                     {this.transformEditor}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2162d8878..5399d38b4 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -122,7 +122,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                 if (highlightIndex) {
                     return {
-                        highlightStyle: doc._isGroup ? "dotted": highlightStyle,
+                        highlightStyle: doc.isGroup ? "dotted": highlightStyle,
                         highlightColor,
                         highlightIndex,
                         highlightStroke: doc.layout_isSvg,
@@ -218,7 +218,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                             ? SettingsManager.userBackgroundColor
                             : doc.annotationOn
                             ? '#00000010' // faint interior for collections on PDFs, images, etc
-                            : doc?._isGroup
+                            : doc?.isGroup
                             ? undefined
                             : doc._type_collection === CollectionViewType.Stacking ?
                             (Colors.DARK_GRAY)
@@ -239,7 +239,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                         doc?.layout_boxShadow,
                         doc?._type_collection === CollectionViewType.Pile
                             ? '4px 4px 10px 2px'
-                            : lockedPosition() || doc?._isGroup || docProps?.LayoutTemplateString
+                            : lockedPosition() || doc?.isGroup || docProps?.LayoutTemplateString
                             ? undefined // groups have no drop shadow -- they're supposed to be "invisible".  LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
                             : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                     );
@@ -264,7 +264,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (props?.pointerEvents?.() === 'none') return 'none';
             if (opacity() === 0) return 'none';
-            if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?._isGroup )? undefined: 'all' 
+            if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.
+                isGroup )? undefined: 'all' 
             if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all';
             return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
         case StyleProp.Decorations:
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f155e64b5..2e8047309 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -19,7 +19,7 @@ import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
 import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
@@ -69,7 +69,7 @@ export class CollectionDockingView extends CollectionSubView() {
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
         DragManager.StartWindowDrag = this.StartOtherDrag;
-        this.rootDoc.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...
+        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...
     }
 
     /**
@@ -381,8 +381,8 @@ export class CollectionDockingView extends CollectionSubView() {
                 this.layoutDoc.dockingConfig = json;
                 this.layoutDoc.data = new List<Doc>(docs);
             } else {
-                Doc.SetInPlace(this.rootDoc, 'dockingConfig', json, true);
-                Doc.SetInPlace(this.rootDoc, 'data', new List<Doc>(docs), true);
+                Doc.SetInPlace(this.Document, 'dockingConfig', json, true);
+                Doc.SetInPlace(this.Document, 'data', new List<Doc>(docs), true);
             }
         }
         this._flush?.end();
@@ -479,9 +479,9 @@ export class CollectionDockingView extends CollectionSubView() {
     tabDestroyed = (tab: any) => {
         this._flush = this._flush ?? UndoManager.StartBatch('tab movement');
         if (tab.DashDoc && ![DocumentType.PRES].includes(tab.DashDoc?.type) && !tab.contentItem.config.props.keyValue) {
-            Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc);
+            Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
             // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
-            if (tab.DashDoc.embedContainer === this.rootDoc) tab.DashDoc.embedContainer = undefined;
+            if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
             if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
             Doc.RemoveDocFromList(Doc.GetProto(tab.DashDoc), 'proto_embeddings', tab.DashDoc);
         }
@@ -512,28 +512,31 @@ export class CollectionDockingView extends CollectionSubView() {
                     _layout_fitWidth: true,
                     title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
                 });
-                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
-                inheritParentAcls(this.rootDoc, docToAdd, false);
+                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+                inheritParentAcls(this.Document, docToAdd, false);
                 CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
             }
         });
 
-        let addNewDoc = action(() => {
-            const dashboard = Doc.ActiveDashboard;
-            if (dashboard) {
-                dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
-                const docToAdd = Docs.Create.FreeformDocument([], {
-                    _width: this.props.PanelWidth(),
-                    _height: this.props.PanelHeight(),
-                    _layout_fitWidth: true,
-                    _freeform_backgroundGrid: true,
-                    title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
-                });
-                Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd);
-                inheritParentAcls(this.dataDoc, docToAdd, false);
-                CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
-            }
-        });
+        let addNewDoc = undoable(
+            action(() => {
+                const dashboard = Doc.ActiveDashboard;
+                if (dashboard) {
+                    dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1;
+                    const docToAdd = Docs.Create.FreeformDocument([], {
+                        _width: this.props.PanelWidth(),
+                        _height: this.props.PanelHeight(),
+                        _layout_fitWidth: true,
+                        _freeform_backgroundGrid: true,
+                        title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
+                    });
+                    Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true);
+                    inheritParentAcls(this.dataDoc, docToAdd, false);
+                    CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
+                }
+            }),
+            'add new tab'
+        );
 
         stack.header?.controlsContainer
             .find('.lm_close') //get the close icon
@@ -569,7 +572,7 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     render() {
-        const href = ImageCast(this.rootDoc.thumb)?.url?.href;
+        const href = ImageCast(this.Document.thumb)?.url?.href;
         return this.props.renderDepth > -1 ? (
             <div>
                 {href ? (
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index b190eb23d..3c555e731 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,43 +1,26 @@
 import React = require('react');
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { ColorState } from 'react-color';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
-import { Document } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
-import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { GestureUtils } from '../../../pen-gestures/GestureUtils';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { AntimodeMenu } from '../AntimodeMenu';
 import { EditableView } from '../EditableView';
-import { GestureOverlay } from '../GestureOverlay';
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke';
-import { LightboxView } from '../LightboxView';
 import { MainView } from '../MainView';
-import { media_state } from '../nodes/AudioBox';
-import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocumentView, DocumentViewInternal, OpenWhereMod } from '../nodes/DocumentView';
-import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionLinearView } from './collectionLinear';
 import './CollectionMenu.scss';
 
@@ -66,7 +49,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     componentDidMount() {
         reaction(
-            () => SelectionManager.Views().length && SelectionManager.Views()[0],
+            () => SelectionManager.Views().lastElement(),
             view => view && this.SetSelection(view)
         );
     }
@@ -174,7 +157,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             </div>
         );
 
-        // NEW BUTTONS
         //dash col linear view buttons
         const contMenuButtons = (
             <div
@@ -189,21 +171,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
         );
 
         return contMenuButtons;
-
-        // const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
-        //     <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
-        //         <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
-        //     </button>
-        // </Tooltip>;
-
-        // //OLD BUTTONS
-        // return this.getElement(!this.SelectedCollection ? [/*button*/] :
-        //     [<CollectionViewBaseChrome key="chrome"
-        //         docView={this.SelectedCollection}
-        //         fieldKey={this.SelectedCollection.LayoutFieldKey}
-        //         type={StrCast(this.SelectedCollection?.props.Document._type_collection, CollectionViewType.Invalid) as CollectionViewType} />,
-        //         prop,
-        //         /*button*/]);
     }
 }
 
@@ -228,7 +195,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _templateCommand = {
         params: ['target', 'source'],
         title: 'item view',
-        script: 'self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])',
+        script: 'this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])',
         immediate: undoBatch((source: Doc[]) => {
             let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text;
             try {
@@ -250,24 +217,24 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _narrativeCommand = {
         params: ['target', 'source'],
         title: 'child click view',
-        script: 'self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])',
+        script: 'this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])',
         immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
         initialize: emptyFunction,
     };
     _contentCommand = {
         params: ['target', 'source'],
         title: 'set content',
-        script: 'getProto(self.target).data = copyField(self.source);',
+        script: 'getProto(this.target).data = copyField(this.source);',
         immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List<Doc>(source))),
         initialize: emptyFunction,
     };
     _onClickCommand = {
         params: ['target', 'proxy'],
         title: 'copy onClick',
-        script: `{ if (self.proxy?.[0]) {
-             getProto(self.proxy[0]).onClick = copyField(self.target.onClick); 
-             getProto(self.proxy[0]).target = self.target.target;
-             getProto(self.proxy[0]).source = copyField(self.target.source); 
+        script: `{ if (this.proxy?.[0]) {
+             getProto(this.proxy[0]).onClick = copyField(this.target.onClick); 
+             getProto(this.proxy[0]).target = this.target.target;
+             getProto(this.proxy[0]).source = copyField(this.target.source); 
             }}`,
         immediate: undoBatch((source: Doc[]) => {}),
         initialize: emptyFunction,
@@ -275,7 +242,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _viewCommand = {
         params: ['target'],
         title: 'bookmark view',
-        script: "self.target._freeform_panX = self['target-freeform_panX']; self.target._freeform_panY = self['target-freeform_panY']; self.target._freeform_scale = self['target_freeform_scale']; gotoFrame(self.target, self['target-currentFrame']);",
+        script: "this.target._freeform_panX = self['target-freeform_panX']; this.target._freeform_panY = this['target-freeform_panY']; this.target._freeform_scale = this['target_freeform_scale']; gotoFrame(this.target, this['target-currentFrame']);",
         immediate: undoBatch((source: Doc[]) => {
             this.target._freeform_panX = 0;
             this.target._freeform_panY = 0;
@@ -292,22 +259,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     _clusterCommand = {
         params: ['target'],
         title: 'fit content',
-        script: 'self.target._freeform_fitContentsToBox = !self.target._freeform_fitContentsToBox;',
+        script: 'this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox;',
         immediate: undoBatch((source: Doc[]) => (this.target._freeform_fitContentsToBox = !this.target._freeform_fitContentsToBox)),
         initialize: emptyFunction,
     };
     _fitContentCommand = {
         params: ['target'],
         title: 'toggle clusters',
-        script: 'self.target._freeform_useClusters = !self.target._freeform_useClusters;',
+        script: 'this.target._freeform_useClusters = !this.target._freeform_useClusters;',
         immediate: undoBatch((source: Doc[]) => (this.target._freeform_useClusters = !this.target._freeform_useClusters)),
         initialize: emptyFunction,
     };
     _saveFilterCommand = {
         params: ['target'],
         title: 'save filter',
-        script: `self.target._childFilters = compareLists(self['target-childFilters'],self.target._childFilters) ? undefined : copyField(self['target-childFilters']); 
-                 self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`,
+        script: `this.target._childFilters = compareLists(this['target-childFilters'],this.target._childFilters) ? undefined : copyField(this['target-childFilters']); 
+        this.target._searchFilterDocs = compareLists(this['target-searchFilterDocs'],this.target._searchFilterDocs) ? undefined: copyField(this['target-searchFilterDocs']);`,
         immediate: undoBatch((source: Doc[]) => {
             this.target._childFilters = undefined;
             this.target._searchFilterDocs = undefined;
@@ -390,57 +357,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
         this.document._facetWidth = 0;
     };
 
-    @computed get subChrome() {
-        switch (
-            this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type // bcz: ARgh!  hack to get menu for tree view outline items
-        ) {
-            default:
-                return this.otherSubChrome;
-            case CollectionViewType.Invalid:
-            case CollectionViewType.Freeform:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />;
-            case CollectionViewType.Stacking:
-                return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.NoteTaking:
-                return <CollectionNoteTakingViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Schema:
-                return <CollectionSchemaViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Tree:
-                return <CollectionTreeViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Masonry:
-                return <CollectionStackingViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Carousel:
-            case CollectionViewType.Carousel3D:
-                return <Collection3DCarouselViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Grid:
-                return <CollectionGridViewChrome key="collchrome" {...this.props} />;
-            case CollectionViewType.Docking:
-                return <CollectionDockingChrome key="collchrome" {...this.props} />;
-        }
-    }
-
-    @computed get otherSubChrome() {
-        const docType = this.props.docView.Document.type;
-        switch (docType) {
-            default:
-                return null;
-            case DocumentType.IMG:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-            case DocumentType.PDF:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-            case DocumentType.INK:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-            case DocumentType.WEB:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-            case DocumentType.VID:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-            case DocumentType.RTF:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />;
-            case DocumentType.MAP:
-                return <CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />;
-        }
-    }
-
     private dropDisposer?: DragManager.DragDropDisposer;
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.dropDisposer?.();
@@ -517,593 +433,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
             </div>
         );
     }
-
-    @computed get viewModes() {
-        const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
-        const isPres: boolean = this.document && this.document.type === DocumentType.PRES;
-        return isPres ? null : (
-            <div className="collectionViewBaseChrome-viewModes">
-                <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom">
-                    <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}>
-                        <button className={'antimodeMenu-button'}>
-                            <FontAwesomeIcon icon="bullseye" size="lg" />
-                        </button>
-                        <select className="collectionViewBaseChrome-viewPicker" onPointerDown={stopPropagation} onChange={this.viewChanged} value={StrCast(this.props.type)}>
-                            {Object.values(CollectionViewType)
-                                .filter(type => !excludedViewTypes.includes(type))
-                                .map(type => (
-                                    <option key={Utils.GenerateGuid()} className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value={type}>
-                                        {type[0].toUpperCase() + type.substring(1)}
-                                    </option>
-                                ))}
-                        </select>
-                    </div>
-                </Tooltip>
-            </div>
-        );
-    }
-
-    @computed get selectedDocumentView() {
-        return SelectionManager.Views().lastElement();
-    }
-    @computed get selectedDoc() {
-        return SelectionManager.Docs().lastElement();
-    }
-    @computed get notACollection() {
-        if (this.selectedDoc) {
-            const layoutField = Doc.LayoutField(this.selectedDoc);
-            return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView'));
-        } else return false;
-    }
-
-    @undoBatch
-    @action
-    startRecording = () => {
-        const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: media_state.PendingRecording });
-        CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
-    };
-
-    @computed
-    get recordButton() {
-        const targetDoc = this.selectedDoc;
-        return (
-            <Tooltip key="record" title={<div className="dash-tooltip">{'Capture screen'}</div>} placement="top">
-                <button className="antimodeMenu-button" onClick={e => this.startRecording()}>
-                    <div className="recordButtonOutline" style={{}}>
-                        <div className="recordButtonInner" style={{}}></div>
-                    </div>
-                </button>
-            </Tooltip>
-        );
-    }
-
-    @undoBatch
-    onEmbed = () => {
-        if (this.selectedDoc && this.selectedDocumentView) {
-            // const copy = Doc.MakeCopy(this.selectedDocumentView.props.Document, true);
-            // copy.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
-            // copy.y = NumCast(this.selectedDoc.y) + 30;
-            // this.selectedDocumentView.props.addDocument?.(copy);
-            const embedding = Doc.MakeEmbedding(this.selectedDoc);
-            embedding.x = NumCast(this.selectedDoc.x) + NumCast(this.selectedDoc._width);
-            embedding.y = NumCast(this.selectedDoc.y) + 30;
-            this.selectedDocumentView.props.addDocument?.(embedding);
-        }
-    };
-    onEmbedButtonDown = (e: React.PointerEvent): void => {
-        setupMoveUpEvents(this, e, this.onEmbedButtonMoved, emptyFunction, emptyFunction);
-    };
-
-    @undoBatch
-    onEmbedButtonMoved = (e: PointerEvent) => {
-        const contentDiv = this.selectedDocumentView?.ContentDiv;
-        if (contentDiv && this.selectedDoc) {
-            const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
-            const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
-            dragData.defaultDropAction = 'embed';
-            dragData.canEmbed = true;
-            DragManager.StartDocumentDrag([contentDiv], dragData, e.clientX, e.clientY, {
-                offsetX: offset[0],
-                offsetY: offset[1],
-                hideSource: false,
-            });
-            return true;
-        }
-        return false;
-    };
-
-    @computed
-    get embedButton() {
-        const targetDoc = this.selectedDoc;
-        return !targetDoc || targetDoc.type === DocumentType.PRES ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'Tap or Drag to create an embedding'}</div>} placement="top">
-                <button className="antimodeMenu-button" onPointerDown={this.onEmbedButtonDown} onClick={this.onEmbed} style={{ cursor: 'drag' }}>
-                    <FontAwesomeIcon className="colMenu-icon" icon="copy" size="lg" />
-                </button>
-            </Tooltip>
-        );
-    }
-
-    @computed get lightboxButton() {
-        const targetDoc = this.selectedDoc;
-        return !targetDoc ? null : (
-            <Tooltip title={<div className="dash-tooltip">{'View in Lightbox'}</div>} placement="top">
-                <button
-                    className="antimodeMenu-button"
-                    onPointerDown={() => {
-                        const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]);
-                        LightboxView.Instance.SetLightboxDoc(targetDoc, undefined, docs);
-                    }}>
-                    <FontAwesomeIcon className="colMenu-icon" icon="desktop" size="lg" />
-                </button>
-            </Tooltip>
-        );
-    }
-
-    @computed get toggleOverlayButton() {
-        return (
-            <>
-                <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom">
-                    <button
-                        className={'antimodeMenu-button'}
-                        key="float"
-                        style={{
-                            backgroundColor: this.props.docView.layoutDoc.z ? '121212' : undefined,
-                            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.CollectionFreeFormDocumentView?.float())}>
-                        <FontAwesomeIcon icon={['fab', 'buffer']} size={'lg'} />
-                    </button>
-                </Tooltip>
-            </>
-        );
-    }
-
     render() {
         return (
             <div className="collectionMenu-cont">
                 <div className="collectionMenu">
-                    <div className="collectionViewBaseChrome">
-                        {this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes}
-                        <div className="collectionMenu-divider" key="divider1"></div>
-                        {this.embedButton}
-                        {/* {this.pinButton} */}
-                        {this.toggleOverlayButton}
-                        <div className="collectionMenu-divider" key="divider2"></div>
-                        {this.subChrome}
-                        <div className="collectionMenu-divider" key="divider3"></div>
-                        {this.lightboxButton}
-                        {this.recordButton}
-                        {!this._buttonizableCommands ? null : this.templateChrome}
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
-
-@observer
-export class CollectionDockingChrome extends React.Component<CollectionViewMenuProps> {
-    render() {
-        return null;
-    }
-}
-
-@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean; isDoc?: boolean }> {
-    public static Instance: CollectionFreeFormViewChrome;
-    constructor(props: any) {
-        super(props);
-        CollectionFreeFormViewChrome.Instance = this;
-    }
-    get document() {
-        return this.props.docView.props.Document;
-    }
-    @computed get dataField() {
-        return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '_annotations' : '')];
-    }
-    @computed get childDocs() {
-        return DocListCast(this.dataField);
-    }
-    @computed get selectedDocumentView() {
-        return SelectionManager.Views().lastElement();
-    }
-    @computed get selectedDoc() {
-        return SelectionManager.Docs().lastElement();
-    }
-    @computed get isText() {
-        return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
-    }
-
-    public static gotoKeyFrame(doc: Doc, newFrame: number) {
-        if (doc) {
-            const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
-            const currentFrame = Cast(doc._currentFrame, 'number', null);
-            if (currentFrame === undefined) {
-                doc._currentFrame = 0;
-                CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
-            }
-            CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
-            doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
-        }
-    }
-
-    _keyTimer: NodeJS.Timeout | undefined;
-    @undoBatch
-    @action
-    nextKeyframe = (): void => {
-        const currentFrame = Cast(this.document._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            this.document._currentFrame = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
-        }
-        this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.document], currentFrame || 0);
-        this.document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
-        this.document.lastFrame = Math.max(NumCast(this.document._currentFrame), NumCast(this.document.lastFrame));
-    };
-    @undoBatch
-    @action
-    prevKeyframe = (): void => {
-        const currentFrame = Cast(this.document._currentFrame, 'number', null);
-        if (currentFrame === undefined) {
-            this.document._currentFrame = 0;
-            CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
-        }
-        this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.document], 1000);
-        this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
-    };
-
-    private _palette = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', ''];
-    private _width = ['1', '5', '10', '100'];
-    private _dotsize = [10, 20, 30, 40];
-    private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O'];
-    private _head = ['', '', '', '', 'arrow', '', ''];
-    private _end = ['', '', '', 'arrow', 'arrow', '', ''];
-    private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle'] as GestureUtils.Gestures[];
-    private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle'];
-    private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle'];
-    @observable _selectedPrimitive = this._shapePrims.length;
-    @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
-    @observable _colorBtn = false;
-    @observable _widthBtn = false;
-    @observable _fillBtn = false;
-
-    @action clearKeepPrimitiveMode() {
-        this._selectedPrimitive = this._shapePrims.length;
-    }
-    @action primCreated() {
-        if (!this._keepPrimitiveMode) {
-            //get out of ink mode after each stroke=
-            if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
-            Doc.ActiveTool = InkTool.None;
-            this._selectedPrimitive = this._shapePrims.length;
-            SetActiveArrowStart('none');
-            SetActiveArrowEnd('none');
-        }
-    }
-
-    @action
-    changeColor = (color: string, type: string) => {
-        const col: ColorState = {
-            hex: color,
-            hsl: { a: 0, h: 0, s: 0, l: 0, source: '' },
-            hsv: { a: 0, h: 0, s: 0, v: 0, source: '' },
-            rgb: { a: 0, r: 0, b: 0, g: 0, source: '' },
-            oldHue: 0,
-            source: '',
-        };
-        if (type === 'color') {
-            SetActiveInkColor(Utils.colorString(col));
-        } else if (type === 'fill') {
-            SetActiveFillColor(Utils.colorString(col));
-        }
-    };
-
-    @action
-    editProperties = (value: any, field: string) => {
-        SelectionManager.Views().forEach(
-            action((element: DocumentView) => {
-                const doc = Document(element.rootDoc);
-                if (doc.type === DocumentType.INK) {
-                    switch (field) {
-                        case 'width':
-                            doc.stroke_width = Number(value);
-                            break;
-                        case 'color':
-                            doc.color = String(value);
-                            break;
-                        case 'fill':
-                            doc.fillColor = String(value);
-                            break;
-                        case 'dash':
-                            doc.stroke_dash = value;
-                    }
-                }
-            })
-        );
-    };
-
-    @computed get drawButtons() {
-        const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => {
-            this._keepPrimitiveMode = keep;
-            // these are for shapes
-            if (this._selectedPrimitive !== i) {
-                this._selectedPrimitive = i;
-                if (this._title[i] === 'highlighter') {
-                    Doc.ActiveTool = InkTool.Highlighter;
-                    GestureOverlay.Instance.SavedColor = ActiveInkColor();
-                    SetActiveInkColor('rgba(245, 230, 95, 0.75)');
-                } else {
-                    Doc.ActiveTool = InkTool.Pen;
-                }
-                SetActiveArrowStart(this._head[i]);
-                SetActiveArrowEnd(this._end[i]);
-                SetActiveBezierApprox('300');
-
-                if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = this._shapePrims[i];
-            } else {
-                this._selectedPrimitive = this._shapePrims.length;
-                Doc.ActiveTool = InkTool.None;
-                SetActiveArrowStart('');
-                SetActiveArrowEnd('');
-                if (GestureOverlay.Instance) GestureOverlay.Instance.InkShape = undefined;
-                SetActiveBezierApprox('0');
-            }
-            e.stopPropagation();
-        });
-        return (
-            <div className="btn-draw" key="draw">
-                {this._draw.map((icon, i) => (
-                    <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom">
-                        <button className="antimodeMenu-button" onPointerDown={e => func(e, i, false)} onDoubleClick={e => func(e, i, true)} style={{ backgroundColor: i === this._selectedPrimitive ? '525252' : '', fontSize: '20' }}>
-                            <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
-                        </button>
-                    </Tooltip>
-                ))}
-            </div>
-        );
-    }
-
-    toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps['icon'], ele: JSX.Element | null) => {
-        return (
-            <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom">
-                <button className="antimodeMenu-button" key={key} onPointerDown={action(e => setter())} style={{ backgroundColor: value ? '121212' : '' }}>
-                    <FontAwesomeIcon icon={icon} size="lg" />
-                    {ele}
-                </button>
-            </Tooltip>
-        );
-    };
-
-    @computed get widthPicker() {
-        const widthPicker = this.toggleButton('stroke width', this._widthBtn, () => (this._widthBtn = !this._widthBtn), 'bars', null);
-        return !this._widthBtn ? (
-            widthPicker
-        ) : (
-            <div className="btn2-group" key="width">
-                {widthPicker}
-                {this._width.map((wid, i) => (
-                    <Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom">
-                        <button
-                            className="antimodeMenu-button"
-                            key={wid}
-                            onPointerDown={action(() => {
-                                SetActiveInkWidth(wid);
-                                this._widthBtn = false;
-                                this.editProperties(wid, 'width');
-                            })}
-                            style={{ backgroundColor: this._widthBtn ? '121212' : '', zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: 'center' }}>
-                            •
-                        </button>
-                    </Tooltip>
-                ))}
-            </div>
-        );
-    }
-
-    @computed get colorPicker() {
-        const colorPicker = this.toggleButton('stroke color', this._colorBtn, () => (this._colorBtn = !this._colorBtn), 'pen-nib', <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? '121212' }} />);
-        return !this._colorBtn ? (
-            colorPicker
-        ) : (
-            <div className="btn-group" key="color">
-                {colorPicker}
-                {this._palette.map(color => (
-                    <button
-                        className="antimodeMenu-button"
-                        key={color}
-                        onPointerDown={action(() => {
-                            this.changeColor(color, 'color');
-                            this._colorBtn = false;
-                            this.editProperties(color, 'color');
-                        })}
-                        style={{ backgroundColor: this._colorBtn ? '121212' : '', zIndex: 1001 }}>
-                        {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
-                        <div className="color-previewII" style={{ backgroundColor: color }}>
-                            {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
-                        </div>
-                    </button>
-                ))}
-            </div>
-        );
-    }
-    @computed get fillPicker() {
-        const fillPicker = this.toggleButton('shape fill color', this._fillBtn, () => (this._fillBtn = !this._fillBtn), 'fill-drip', <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? '121212' }} />);
-        return !this._fillBtn ? (
-            fillPicker
-        ) : (
-            <div className="btn-group" key="fill">
-                {fillPicker}
-                {this._palette.map(color => (
-                    <button
-                        className="antimodeMenu-button"
-                        key={color}
-                        onPointerDown={action(() => {
-                            this.changeColor(color, 'fill');
-                            this._fillBtn = false;
-                            this.editProperties(color, 'fill');
-                        })}
-                        style={{ backgroundColor: this._fillBtn ? '121212' : '', zIndex: 1001 }}>
-                        <div className="color-previewII" style={{ backgroundColor: color }}>
-                            {color === '' ? <p style={{ fontSize: 40, color: 'red', marginTop: -10, marginLeft: -5, position: 'fixed' }}>☒</p> : ''}
-                        </div>
-                    </button>
-                ))}
-            </div>
-        );
-    }
-
-    render() {
-        return !this.props.docView.layoutDoc ? null : (
-            <div className="collectionFreeFormMenu-cont">
-                <RichTextMenu key="rich" />
-                {!this.isText ? (
-                    <>
-                        {this.drawButtons}
-                        {this.widthPicker}
-                        {this.colorPicker}
-                        {this.fillPicker}
-                        {Doc.noviceMode || this.props.isDoc ? null : (
-                            <>
-                                <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom">
-                                    <div className="backKeyframe" onClick={this.prevKeyframe}>
-                                        <FontAwesomeIcon icon={'caret-left'} size={'lg'} />
-                                    </div>
-                                </Tooltip>
-                                <Tooltip key="num" title={<div className="dash-tooltip">Frame number</div>} placement="bottom">
-                                    <div
-                                        className="numKeyframe"
-                                        style={{ color: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? 'white' : 'black', backgroundColor: this.props.docView.ComponentView?.getKeyFrameEditing?.() ? '#5B9FDD' : '#AEDDF8' }}
-                                        onClick={action(() => this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))}>
-                                        {NumCast(this.document._currentFrame)}
-                                    </div>
-                                </Tooltip>
-                                <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom">
-                                    <div className="fwdKeyframe" onClick={this.nextKeyframe}>
-                                        <FontAwesomeIcon icon={'caret-right'} size={'lg'} />
-                                    </div>
-                                </Tooltip>
-                            </>
-                        )}
-                    </>
-                ) : null}
-                {!this.selectedDocumentView?.ComponentView?.menuControls ? null : this.selectedDocumentView?.ComponentView?.menuControls?.()}
-            </div>
-        );
-    }
-}
-@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewMenuProps> {
-    @observable private _currentKey: string = '';
-    @observable private suggestions: string[] = [];
-
-    get document() {
-        return this.props.docView.props.Document;
-    }
-
-    @computed private get descending() {
-        return StrCast(this.document._columnsSort) === 'descending';
-    }
-    @computed get pivotField() {
-        return StrCast(this.document._pivotField);
-    }
-
-    getKeySuggestions = async (value: string): Promise<string[]> => {
-        const val = value.toLowerCase();
-        const docs = DocListCast(this.document[this.props.fieldKey]);
-
-        if (Doc.noviceMode) {
-            if (docs instanceof Doc) {
-                const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_'));
-                return keys.filter(key => key.toLowerCase().indexOf(val) > -1);
-            }
-            const keys = new Set<string>();
-            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-            const noviceKeys = Array.from(keys).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('author_date') >= 0 || key.indexOf('modificationDate') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_'));
-            return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1);
-        }
-
-        if (docs instanceof Doc) {
-            return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1);
-        } else {
-            const keys = new Set<string>();
-            docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
-            return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1);
-        }
-    };
-
-    @action
-    onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
-        this._currentKey = newValue;
-    };
-
-    getSuggestionValue = (suggestion: string) => suggestion;
-
-    renderSuggestion = (suggestion: string) => {
-        return <p>{suggestion}</p>;
-    };
-
-    onSuggestionFetch = async ({ value }: { value: string }) => {
-        const sugg = await this.getKeySuggestions(value);
-        runInAction(() => {
-            this.suggestions = sugg;
-        });
-    };
-
-    @action
-    onSuggestionClear = () => {
-        this.suggestions = [];
-    };
-
-    @action
-    setValue = (value: string) => {
-        this.document._pivotField = value;
-        return true;
-    };
-
-    @action toggleSort = () => {
-        this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending';
-    };
-    @action resetValue = () => {
-        this._currentKey = this.pivotField;
-    };
-
-    render() {
-        const doctype = this.props.docView.Document.type;
-        const isPres: boolean = doctype === DocumentType.PRES;
-        return isPres ? null : (
-            <div className="collectionStackingViewChrome-cont">
-                <div className="collectionStackingViewChrome-pivotField-cont">
-                    <div className="collectionStackingViewChrome-pivotField-label">GROUP BY:</div>
-                    <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? '180' : '0'}deg)` }}>
-                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
-                    </div>
-                    <div className="collectionStackingViewChrome-pivotField">
-                        <EditableView
-                            GetValue={() => this.pivotField}
-                            autosuggestProps={{
-                                resetValue: this.resetValue,
-                                value: this._currentKey,
-                                onChange: this.onKeyChange,
-                                autosuggestProps: {
-                                    inputProps: {
-                                        value: this._currentKey,
-                                        onChange: this.onKeyChange,
-                                    },
-                                    getSuggestionValue: this.getSuggestionValue,
-                                    suggestions: this.suggestions,
-                                    alwaysRenderSuggestions: true,
-                                    renderSuggestion: this.renderSuggestion,
-                                    onSuggestionsFetchRequested: this.onSuggestionFetch,
-                                    onSuggestionsClearRequested: this.onSuggestionClear,
-                                },
-                            }}
-                            oneLine
-                            SetValue={this.setValue}
-                            contents={this.pivotField ? this.pivotField : 'N/A'}
-                        />
-                    </div>
+                    <div className="collectionViewBaseChrome">{!this._buttonizableCommands ? null : this.templateChrome}</div>
                 </div>
             </div>
         );
@@ -1230,123 +564,6 @@ export class CollectionNoteTakingViewChrome extends React.Component<CollectionVi
     }
 }
 
-@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
-    // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
-    get document() {
-        return this.props.docView.props.Document;
-    }
-
-    @undoBatch
-    togglePreview = () => {
-        const dividerWidth = 4;
-        const borderWidth = 0;
-        const panelWidth = this.props.docView.props.PanelWidth();
-        const previewWidth = NumCast(this.document.schema_previewWidth);
-        const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
-        this.document.schema_previewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
-    };
-
-    @undoBatch
-    @action
-    toggleTextwrap = async () => {
-        const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []);
-        if (textwrappedRows.length) {
-            this.document.textwrappedSchemaRows = new List<string>([]);
-        } else {
-            const docs = DocListCast(this.document[this.props.fieldKey]);
-            const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
-            this.document.textwrappedSchemaRows = new List<string>(allRows);
-        }
-    };
-
-    render() {
-        const previewWidth = NumCast(this.document.schema_previewWidth);
-        const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0;
-
-        return (
-            <div className="collectionSchemaViewChrome-cont">
-                <div className="collectionSchemaViewChrome-toggle">
-                    <div className="collectionSchemaViewChrome-label">Show Preview: </div>
-                    <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
-                        <div className={'collectionSchemaViewChrome-togglerButton' + (previewWidth !== 0 ? ' on' : ' off')}>{previewWidth !== 0 ? 'on' : 'off'}</div>
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
-
-@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewMenuProps> {
-    get document() {
-        return this.props.docView.props.Document;
-    }
-    get sortAscending() {
-        return this.document[this.props.fieldKey + '-sortAscending'];
-    }
-    set sortAscending(value) {
-        this.document[this.props.fieldKey + '-sortAscending'] = value;
-    }
-    @computed private get ascending() {
-        return Cast(this.sortAscending, 'boolean', null);
-    }
-
-    @action toggleSort = () => {
-        if (this.sortAscending) this.sortAscending = undefined;
-        else if (this.sortAscending === undefined) this.sortAscending = false;
-        else this.sortAscending = true;
-    };
-
-    render() {
-        return (
-            <div className="collectionTreeViewChrome-cont">
-                <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
-                    <div className="collectionTreeViewChrome-sortLabel">Sort</div>
-                    <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? '90' : this.ascending ? '180' : '0'}deg)` }}>
-                        <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
-                    </div>
-                </button>
-            </div>
-        );
-    }
-}
-
-// Enter scroll speed for 3D Carousel
-@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionViewMenuProps> {
-    get document() {
-        return this.props.docView.props.Document;
-    }
-    @computed get scrollSpeed() {
-        return this.document._autoScrollSpeed;
-    }
-
-    @action
-    setValue = (value: string) => {
-        const numValue = Number(StrCast(value));
-        if (numValue > 0) {
-            this.document._autoScrollSpeed = numValue;
-            return true;
-        }
-        return false;
-    };
-
-    render() {
-        return (
-            <div className="collection3DCarouselViewChrome-cont">
-                <div className="collection3DCarouselViewChrome-scrollSpeed-cont">
-                    {/* {FormattedTextBox.Focused ? <RichTextMenu /> : null} */}
-                    <div className="collectionStackingViewChrome-scrollSpeed-label">AUTOSCROLL SPEED:</div>
-                    <div className="collection3DCarouselViewChrome-scrollSpeed">
-                        <EditableView GetValue={() => StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
-
 /**
  * Chrome for grid view.
  */
@@ -1492,12 +709,6 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
                     <input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" />
                     <input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" />
                 </span>
-                {/* <span className="grid-control">
-                    <span className="grid-icon">
-                        <FontAwesomeIcon icon="text-height" size="1x" />
-                    </span>
-                    <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
-                </span> */}
                 <span className="grid-control" style={{ width: this.resize ? '12%' : '20%' }}>
                     <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
                     <label className="flexLabel">{this.resize ? 'Coll' : 'Collisions'}</label>
@@ -1526,6 +737,3 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu
         );
     }
 }
-ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
-    CollectionFreeFormViewChrome.gotoKeyFrame(doc, newFrame);
-});
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index ac916fef3..e99bc2f08 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -229,8 +229,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             <DocumentView
                 ref={r => (dref = r || undefined)}
                 Document={doc}
-                pointerEvents={this.blockPointerEventsWhenDragging}
                 DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
+                pointerEvents={this.blockPointerEventsWhenDragging}
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
@@ -635,7 +635,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 key="notes"
                 style={{
                     overflowY: this.props.isContentActive() ? 'auto' : 'hidden',
-                    background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+                    background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor),
                     pointerEvents: this.backgroundEvents,
                 }}
                 onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index abb12a8ab..ad8144466 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.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc);
+        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.Document);
         return ret;
     };
 
@@ -78,12 +78,12 @@ export class CollectionPileView extends CollectionSubView() {
     toggleStarburst = action(() => {
         this.layoutDoc._freeform_scale = undefined;
         if (this.layoutEngine() === computeStarburstLayout.name) {
-            if (NumCast(this.rootDoc._width) !== NumCast(this.rootDoc._starburstDiameter, 500)) {
-                this.rootDoc._starburstDiameter = NumCast(this.rootDoc._width);
+            if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) {
+                this.Document._starburstDiameter = NumCast(this.layoutDoc._width);
             }
             const defaultSize = 110;
-            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.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+            this.Document.y = NumCast(this.Document.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);
@@ -91,9 +91,9 @@ export class CollectionPileView extends CollectionSubView() {
             this.layoutDoc._freeform_panY = -10;
             this.props.Document._freeform_pileEngine = computePassLayout.name;
         } else {
-            const defaultSize = NumCast(this.rootDoc._starburstDiameter, 400);
-            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;
+            const defaultSize = NumCast(this.Document._starburstDiameter, 400);
+            this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+            this.Document.y = NumCast(this.Document.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;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 3351ca48e..7bde2cb5f 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -192,7 +192,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         CollectionStackedTimeline.SelectingRegion = this;
                     } else {
                         this._markerEnd = this.currentTime;
-                        CollectionStackedTimeline.createAnchor(this.rootDoc, 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;
                     }
@@ -258,7 +258,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         this._markerEnd = tmp;
                     }
                     if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
-                        const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, 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);
@@ -274,7 +274,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 undefined,
                 () => {
                     if (shiftKey) {
-                        CollectionStackedTimeline.createAnchor(this.rootDoc, 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));
                     }
@@ -388,8 +388,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     // creates marker on timeline
     @undoBatch
     @action
-    static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
-        if (anchorStartTime === undefined) return rootDoc;
+    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';
         const endTag = '_timecodeToHide';
         const anchor =
@@ -402,7 +402,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 backgroundColor: 'rgba(128, 128, 128, 0.5)',
                 layout_hideLinkButton: true,
                 onClick: FollowLinkScript(),
-                annotationOn: rootDoc,
+                annotationOn: doc,
                 _isTimelineLabel: true,
                 layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined,
             });
@@ -539,7 +539,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 <div
                     className="collectionStackedTimeline-timelineContainer"
                     style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
-                    onWheel={e => e.stopPropagation()}
+                    onWheel={e => this.isContentActive() && e.stopPropagation()}
                     onScroll={this.setScroll}
                     onMouseMove={e => this.isContentActive() && this.onHover(e)}
                     ref={wrapper => (this._timelineWrapper = wrapper)}>
@@ -775,7 +775,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // context menu
     contextMenuItems = () => {
         const resetTitle = {
-            script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${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',
         };
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index dddb3ec71..165ccbdb1 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -142,7 +142,7 @@
         transform-origin: top left;
         grid-column-end: span 1;
         height: 100%;
-        margin: auto;
+        //margin: auto;
         display: inline-grid;
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index da00093dd..5b86aaf86 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -9,7 +9,7 @@ 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 { 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 { Docs, DocUtils } from '../../documents/Documents';
@@ -309,12 +309,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             : undefined;
     isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.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));
     // 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 ? undefined : this.props.DataDoc;
         const height = () => this.getDocHeight(doc);
-
+        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 });
         return count > this._renderCount ? null : (
@@ -323,21 +325,21 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 Document={doc}
                 DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
                 renderDepth={this.props.renderDepth + 1}
-                PanelWidth={width}
-                PanelHeight={height}
+                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)
                 styleProvider={this.styleProvider}
                 docViewPath={this.props.docViewPath}
-                layout_fitWidth={this.props.childLayoutFitWidth}
+                layout_fitWidth={this.childFitWidth}
                 isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
                 onKey={this.onKeyDown}
                 onBrowseClick={this.props.onBrowseClick}
                 isDocumentActive={this.isContentActive}
                 LayoutTemplate={this.props.childLayoutTemplate}
                 LayoutTemplateString={this.props.childLayoutString}
-                NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childLayoutFitWidth?.(doc) || (doc._layout_fitWidth && !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) || (doc._layout_fitWidth && !Doc.NativeHeight(doc)) ? height : undefined}
-                dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined}
+                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}
@@ -377,7 +379,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         if (!d) return 0;
         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))) {
+        if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) {
             return Math.min(NumCast(d._width), maxWidth);
         }
         return maxWidth;
@@ -387,15 +389,15 @@ 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)) ? NumCast(d._width) : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0);
+        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) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
             return Math.min(maxHeight, (docWid * nh) / nw);
         }
         const childHeight = NumCast(childLayoutDoc._height);
-        const panelHeight = childLayoutDoc._layout_fitWidth || this.props.childLayoutFitWidth?.(d) ? 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);
     }
 
@@ -664,41 +666,35 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
 
     return35 = () => 35;
     @computed get buttonMenu() {
-        const menuDoc: Doc = Cast(this.rootDoc.layout_headerButton, Doc, null);
-        // TODO:glr Allow support for multiple buttons
-        if (menuDoc) {
-            const width: number = NumCast(menuDoc._width, 30);
-            const height: number = NumCast(menuDoc._height, 30);
-            return (
-                <div className="buttonMenu-docBtn" style={{ width: width, height: height }}>
-                    <DocumentView
-                        Document={menuDoc}
-                        DataDoc={menuDoc}
-                        isContentActive={this.isContentActive}
-                        isDocumentActive={this.isContentActive}
-                        addDocument={this.props.addDocument}
-                        moveDocument={this.props.moveDocument}
-                        addDocTab={this.props.addDocTab}
-                        onBrowseClick={this.props.onBrowseClick}
-                        pinToPres={emptyFunction}
-                        rootSelected={this.props.isSelected}
-                        removeDocument={this.props.removeDocument}
-                        ScreenToLocalTransform={Transform.Identity}
-                        PanelWidth={this.return35}
-                        PanelHeight={this.return35}
-                        renderDepth={this.props.renderDepth}
-                        focus={emptyFunction}
-                        styleProvider={this.props.styleProvider}
-                        docViewPath={returnEmptyDoclist}
-                        whenChildContentsActiveChanged={emptyFunction}
-                        bringToFront={emptyFunction}
-                        childFilters={this.props.childFilters}
-                        childFiltersByRanges={this.props.childFiltersByRanges}
-                        searchFilterDocs={this.props.searchFilterDocs}
-                    />
-                </div>
-            );
-        }
+        const menuDoc = DocCast(this.layoutDoc.layout_headerButton);
+        return !menuDoc ? null : (
+            <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
+                <DocumentView
+                    Document={menuDoc}
+                    isContentActive={this.isContentActive}
+                    isDocumentActive={this.isContentActive}
+                    addDocument={this.props.addDocument}
+                    moveDocument={this.props.moveDocument}
+                    addDocTab={this.props.addDocTab}
+                    onBrowseClick={this.props.onBrowseClick}
+                    pinToPres={emptyFunction}
+                    rootSelected={this.props.isSelected}
+                    removeDocument={this.props.removeDocument}
+                    ScreenToLocalTransform={Transform.Identity}
+                    PanelWidth={this.return35}
+                    PanelHeight={this.return35}
+                    renderDepth={this.props.renderDepth}
+                    focus={emptyFunction}
+                    styleProvider={this.props.styleProvider}
+                    docViewPath={returnEmptyDoclist}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    bringToFront={emptyFunction}
+                    childFilters={this.props.childFilters}
+                    childFiltersByRanges={this.props.childFiltersByRanges}
+                    searchFilterDocs={this.props.searchFilterDocs}
+                />
+            </div>
+        );
     }
 
     @computed get nativeWidth() {
@@ -723,8 +719,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             SetValue: this.addGroup,
             contents: '+ ADD A GROUP',
         };
-        const buttonMenu = this.rootDoc.layout_headerButton;
-        const noviceExplainer = this.rootDoc.layout_explainer;
+        const buttonMenu = this.layoutDoc.layout_headerButton;
+        const noviceExplainer = this.layoutDoc.layout_explainer;
         return (
             <>
                 {buttonMenu || noviceExplainer ? (
@@ -739,7 +735,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                         ref={this.createRef}
                         style={{
                             overflowY: this.isContentActive() ? 'auto' : 'hidden',
-                            background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+                            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))}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9626f06ca..8896e2d61 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -17,15 +17,14 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'
 import { Networking } from '../../Network';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { InteractionUtils } from '../../util/InteractionUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { DocComponent } from '../DocComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
+import { LoadingBox } from '../nodes/LoadingBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
 import React = require('react');
-import { LoadingBox } from '../nodes/LoadingBox';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -60,7 +59,8 @@ export function CollectionSubView<X>(moreProps?: X) {
             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 = () => this.props.isSelected() || (this.rootDoc && this.props.rootSelected());
+        // this returns whether either the collection is selected, or the template that it is part of is selected
+        rootSelected = () => this.props.isSelected() || 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
@@ -100,7 +100,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             } else if (Cast(this.dataField, listSpec(Doc), null)) {
                 // otherwise, if the collection data is a list, then use it.
                 rawdocs = Cast(this.dataField, listSpec(Doc), null);
-            } else {
+            } else if (this.dataField) {
                 // 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.
@@ -217,18 +217,18 @@ export function CollectionSubView<X>(moreProps?: X) {
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     if (movedDocs.length) {
                         const canAdd =
-                            (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.rootDoc)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.rootDoc));
-                        const moved = docDragData.moveDocument(movedDocs, this.rootDoc, canAdd ? this.addDocument : returnFalse);
+                            (de.embedKey || dropAction || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.Document)) && (dropAction !== 'inSame' || docDragData.draggedDocuments.every(d => d.embedContainer === this.Document));
+                        const moved = docDragData.moveDocument(movedDocs, this.Document, canAdd ? this.addDocument : returnFalse);
                         added = canAdd || moved ? moved : undefined;
                     } else if (addedDocs.length) {
                         added = this.addDocument(addedDocs);
                     }
-                    if (!added && ScriptCast(this.rootDoc.dropConverter)) {
-                        ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
+                    if (!added && ScriptCast(this.Document.dropConverter)) {
+                        ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData });
                         added = addedDocs.length ? this.addDocument(addedDocs) : true;
                     }
                 } else {
-                    ScriptCast(this.rootDoc.dropConverter)?.script.run({ dragData: docDragData });
+                    ScriptCast(this.Document.dropConverter)?.script.run({ dragData: docDragData });
                     added = this.addDocument(docDragData.droppedDocuments);
                     !added && alert('You cannot perform this move');
                 }
@@ -238,7 +238,7 @@ export function CollectionSubView<X>(moreProps?: X) {
             } else if (de.complete.annoDragData) {
                 const dropCreator = de.complete.annoDragData.dropDocCreator;
                 de.complete.annoDragData.dropDocCreator = () => {
-                    const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined);
+                    const dropped = dropCreator(this.props.isAnnotationOverlay ? this.Document : undefined);
                     this.addDocument(dropped);
                     return dropped;
                 };
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index a8f5345b7..1cdd200e5 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -47,9 +47,9 @@ export class CollectionTimeView extends CollectionSubView() {
     getAnchor = (addAsAnnotation: boolean) => {
         const anchor = Docs.Create.HTMLMarkerDocument([], {
             title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
-            annotationOn: this.rootDoc,
+            annotationOn: this.Document,
         });
-        PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.rootDoc);
+        PresBox.pinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document);
 
         if (addAsAnnotation) {
             // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 54332a7fd..b34598731 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -101,7 +101,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     componentDidMount() {
         //this.props.setContentView?.(this);
         this._disposers.autoheight = reaction(
-            () => this.rootDoc.layout_autoHeight,
+            () => this.layoutDoc.layout_autoHeight,
             auto => auto && this.computeHeight(),
             { fireImmediately: true }
         );
@@ -117,19 +117,19 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     };
     unobserveHeight = (ref: any) => {
         this.refList.delete(ref);
-        this.rootDoc.layout_autoHeight && this.computeHeight();
+        this.layoutDoc.layout_autoHeight && this.computeHeight();
     };
     observeHeight = (ref: any) => {
         if (ref) {
             this.refList.add(ref);
             this.observer = new _global.ResizeObserver(
                 action((entries: any) => {
-                    if (this.rootDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
+                    if (this.layoutDoc.layout_autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) {
                         this.computeHeight();
                     }
                 })
             );
-            this.rootDoc.layout_autoHeight && this.computeHeight();
+            this.layoutDoc.layout_autoHeight && this.computeHeight();
             this.observer.observe(ref);
         }
     };
@@ -142,7 +142,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const dropAction = this.layoutDoc.dropAction as dropActionType;
         const dragData = de.complete.docDragData;
         if (dragData) {
-            const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.rootDoc) ? true : false;
+            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));
             if (isAlreadyInTree() !== sameTree) {
                 console.log('WHAAAT');
@@ -160,7 +160,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const targetDataDoc = this.doc[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.rootDoc, doc)))) SelectionManager.DeselectAll();
+        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];
@@ -316,12 +316,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     }
 
     @computed get noviceExplainer() {
-        return !Doc.noviceMode || !this.rootDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.rootDoc.layout_explainer)} </div>;
+        return !Doc.noviceMode || !this.layoutDoc.layout_explainer ? null : <div className="documentExplanation"> {StrCast(this.layoutDoc.layout_explainer)} </div>;
     }
 
     return35 = () => 35;
     @computed get buttonMenu() {
-        const menuDoc = Cast(this.rootDoc.layout_headerButton, Doc, null);
+        const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null);
         // To create a multibutton menu add a CollectionLinearView
         return !menuDoc ? null : (
             <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 426ad2c2f..4f3c5b9a2 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -34,6 +34,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import React = require('react');
+import { Docs } from '../../documents/Documents';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -259,7 +260,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 return;
             }
             const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps);
-            const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Doc.MakeDelegate(anchorDoc && anchorDoc !== doc ? anchorDoc : doc);
+            const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({});
             pinDoc.presentation_targetDoc = anchorDoc ?? doc;
             pinDoc.title = doc.title + ' - Slide';
             pinDoc.data = new List<Doc>(); // the children of the embedding's layout are the presentation slide children.  the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
@@ -271,7 +272,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.treeView = ''; // not really needed, but makes key value pane look better
             pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
             pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling.
-            pinDoc.treeView_ChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
             pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
             pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
             pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6e3f8e807..9de74b761 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -19,7 +19,6 @@ import { DocumentManager } from '../../util/DocumentManager';
 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';
@@ -144,7 +143,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         return NumCast(this.props.treeViewParent.maxEmbedHeight, 200);
     }
     @computed get dataDoc() {
-        return this.props.document.treeView_ChildrenOnRoot ? this.doc : this.doc[DocData];
+        return this.doc[DocData];
     }
     @computed get layoutDoc() {
         return Doc.Layout(this.doc);
@@ -333,7 +332,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.rootDoc) {
+        if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.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';
@@ -361,7 +360,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             _width: 1000,
             _height: 10,
         });
-        Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
+        Doc.GetProto(bullet).title = ComputedField.MakeFunction('this.text?.Text');
         Doc.GetProto(bullet).data = new List<Doc>([]);
         DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.());
 
@@ -838,7 +837,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 ? [openEmbedding, makeFolder]
                 : this.doc._type_collection === CollectionViewType.Docking
                 ? []
-                : this.props.treeView.rootDoc === Doc.MyRecentlyClosed
+                : this.props.treeView.Document === Doc.MyRecentlyClosed
                 ? [reopenDoc]
                 : [openEmbedding, focusDoc]),
         ];
@@ -882,8 +881,8 @@ 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.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
-                            paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            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,
                         }}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 403fba67b..d6a738084 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -37,6 +37,7 @@ export interface PoolData {
     zIndex?: number;
     width?: number;
     height?: number;
+    autoDim?: number; // 1 for set to Panel dims, 0 for use width/height as entered
     backgroundColor?: string;
     color?: string;
     opacity?: number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
index 856e195a3..38aeea152 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -8,7 +8,7 @@ import React = require('react');
 import { CollectionFreeFormView } from './CollectionFreeFormView';
 
 export interface CollectionFreeFormPannableContentsProps {
-    rootDoc: Doc;
+    Document: Doc;
     viewDefDivClick?: ScriptField;
     children?: React.ReactNode | undefined;
     transition?: string;
@@ -20,7 +20,7 @@ export interface CollectionFreeFormPannableContentsProps {
 @observer
 export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
     @computed get presPaths() {
-        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
+        return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.Document) : 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) =>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7f8bde9b4..6babe3c8f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -134,7 +134,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
     }
     @computed get contentBounds() {
-        const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
+        const cb = Cast(this.dataDoc.contentBounds, listSpec('number'));
         return cb
             ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
             : aggregateBounds(
@@ -155,9 +155,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
     @computed get cachedCenteringShiftY(): number {
         const dv = this.props.DocumentView?.();
+        const fitWidth = this.props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
         const scaling = this.fitContentsToBox || !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 && !dv.layoutDoc.layout_fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
+        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() {
@@ -181,15 +182,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const timecode = Math.round(time);
         docs.forEach(doc => {
             CollectionFreeFormDocumentView.animFields.forEach(val => {
-                const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
+                const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null);
                 findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
             });
             CollectionFreeFormDocumentView.animStringFields.forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+                const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null);
                 findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
             });
             CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
-                const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+                const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null);
                 findexed?.length <= timecode + 1 && findexed.push(undefined as any);
             });
         });
@@ -262,7 +263,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             for (const newBox of newBoxes) {
                 if (newBox.activeFrame !== undefined) {
                     const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]);
-                    CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}-indexed`]);
+                    CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}_indexed`]);
                     CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]);
                     delete newBox.activeFrame;
                     CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i]));
@@ -283,15 +284,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     groupFocus = (anchor: Doc, options: DocFocusOptions) => {
-        options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
-        const res = this.props.focus(this.rootDoc, options);
+        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);
         options.docTransform = undefined;
         return res;
     };
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (this._lightboxDoc) return;
-        if (anchor === this.rootDoc) {
+        if (anchor === this.Document) {
             if (options.willZoomCentered && options.zoomScale) {
                 this.fitContentOnce();
                 options.didMove = true;
@@ -300,7 +301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
-        const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+        const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
         const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
 
         // focus on the document in the collection
@@ -407,7 +408,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             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.rootDoc
+                !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
                     ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' })
                     : Docs.Create.FontIconDocument({
                           title: 'anchor',
@@ -582,27 +583,28 @@ 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
-        switch (property) {
-            case StyleProp.BackgroundColor:
-                const cluster = NumCast(doc?.layout_cluster);
-                if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
-                    if (this._clusterSets.length <= cluster) {
-                        setTimeout(() => doc && this.updateCluster(doc));
-                    } else {
-                        // choose a cluster color from a palette
-                        const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
-                        styleProp = colors[cluster % colors.length];
-                        const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
-                        // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
-                        set?.map(s => (styleProp = StrCast(s.backgroundColor)));
+        if (doc && this.childDocList?.includes(doc))
+            switch (property) {
+                case StyleProp.BackgroundColor:
+                    const cluster = NumCast(doc?.layout_cluster);
+                    if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
+                        if (this._clusterSets.length <= cluster) {
+                            setTimeout(() => doc && this.updateCluster(doc));
+                        } else {
+                            // choose a cluster color from a palette
+                            const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)'];
+                            styleProp = colors[cluster % colors.length];
+                            const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
+                            // override the cluster color with an explicitly set color on a non-background document.  then override that with an explicitly set color on a background document
+                            set?.map(s => (styleProp = StrCast(s.backgroundColor)));
+                        }
                     }
-                }
-                break;
-            case StyleProp.FillColor:
-                if (doc && this.Document._currentFrame !== undefined) {
-                    return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
-                }
-        }
+                    break;
+                case StyleProp.FillColor:
+                    if (doc && this.Document._currentFrame !== undefined) {
+                        return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+                    }
+            }
         return styleProp;
     };
 
@@ -623,7 +625,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         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 (!this.props.Document._isGroup) {
+            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
                 switch (Doc.ActiveTool) {
@@ -697,7 +699,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @action
     onEraserUp = (e: PointerEvent): void => {
-        this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc));
+        this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.Document));
         this._deleteList = [];
         this._batch?.end();
     };
@@ -752,8 +754,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
             if (!this._deleteList.includes(intersect.inkView)) {
                 this._deleteList.push(intersect.inkView);
-                SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.stroke_width?.toString()) || '1');
-                SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
+                SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
+                SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
                 // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
                 if (!e.shiftKey) {
                     this._eraserLock++;
@@ -785,7 +787,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.rootDoc._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
         }
@@ -927,7 +929,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);
@@ -935,15 +937,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
-        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) {
+        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.Document[this.scaleFieldKey + '_min'])) {
             this.setPan(0, 0);
             return;
         }
-        if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
-            deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
+        if (deltaScale * invTransform.Scale > NumCast(this.Document[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) {
+            deltaScale = NumCast(this.Document[this.scaleFieldKey + '_max'], 1) / invTransform.Scale;
         }
-        if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
-            deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
+        if (deltaScale * invTransform.Scale < NumCast(this.Document[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) {
+            deltaScale = NumCast(this.Document[this.scaleFieldKey + '_min'], 1) / invTransform.Scale;
         }
 
         const localTransform = invTransform.scaleAbout(deltaScale, x, y);
@@ -956,11 +958,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+        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;
         e.stopPropagation();
-        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+        const docHeight = NumCast(this.rootDoc[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;
         switch (
             !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?//
@@ -1024,28 +1026,28 @@ 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 minScale = NumCast(this.dataDoc._freeform_scale_min, 1);
             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);
+            const minPanX = NumCast(this.dataDoc._freeform_panX_min, 0);
+            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 maxPanY =
                 minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
-                scale * NumCast(this.rootDoc._panY_max, nativeHeight) +
+                scale * NumCast(this.dataDoc._panY_max, nativeHeight) +
                 (!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.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
-                const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
-                this.rootDoc.layout_scrollTop = undefined;
+            if (false && NumCast(this.layoutDoc.layout_scrollTop) && NumCast(this.layoutDoc._freeform_scale, minScale) !== minScale) {
+                const relTop = NumCast(this.layoutDoc.layout_scrollTop) / maxScrollTop;
+                this.layoutDoc.layout_scrollTop = undefined;
                 newPanY = minPanY + relTop * (maxPanY - minPanY);
-            } else if (fitYscroll > 2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+            } else if (fitYscroll > 2 && this.layoutDoc.layout_scrollTop === undefined && NumCast(this.layoutDoc._freeform_scale, minScale) === minScale) {
                 const maxPanY = minPanY + fitYscroll;
                 const relTop = (panY - minPanY) / (maxPanY - minPanY);
-                setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+                setTimeout(() => (this.layoutDoc.layout_scrollTop = relTop * maxScrollTop), 10);
                 newPanY = minPanY;
             }
             !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
@@ -1055,8 +1057,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
-        const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
-        if (collectionDoc?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) {
+        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(),
@@ -1104,7 +1106,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     @action
     zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
-        if (this.Document._isGroup) return;
+        if (this.Document.isGroup) return;
         this.setPanZoomTransition(transitionTime);
         const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
         this.layoutDoc[this.scaleFieldKey] = scale;
@@ -1162,17 +1164,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @action
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
         const docView = fieldProps.DocumentView?.();
-        if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
+        if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
             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 (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
-            else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
-            if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
-                newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
+            if (below) newDoc.y = NumCast(docView.Document.y) + NumCast(docView.Document._height) + 10;
+            else newDoc.x = NumCast(docView.Document.x) + NumCast(docView.Document._width) + 10;
+            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];
@@ -1228,7 +1230,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 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}
@@ -1237,7 +1239,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 docViewPath={this.props.docViewPath}
                 styleProvider={this.clusterStyleProvider}
-                dragAction={(this.rootDoc.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}
@@ -1288,9 +1290,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
         const { z, zIndex } = childDoc;
         const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
-        const { x, y, _width, _height, opacity, _rotation } =
+        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)
-                ? { _width: -1, _height: -1, _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',
@@ -1300,6 +1302,7 @@ 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'),
+            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),
@@ -1413,8 +1416,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
-        const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.rootDoc });
-        PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
+        const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document });
+        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) {
@@ -1434,7 +1437,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._firstRender = false;
                 this._disposers.groupBounds = reaction(
                     () => {
-                        if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+                        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: NumCast(cd._width), height: NumCast(cd._height) }));
                             return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
                         }
@@ -1481,7 +1484,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
                 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()
+                    active => !SnappingManager.GetIsDragging() && this.dataDoc[this.autoResetFieldKey] && active && this.resetView()
                 );
             })
         );
@@ -1575,7 +1578,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     componentWillUnmount() {
-        this.rootDoc[this.autoResetFieldKey] && this.resetView();
+        this.dataDoc[this.autoResetFieldKey] && this.resetView();
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
@@ -1616,9 +1619,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     ///
     @undoBatch
     resetView = () => {
-        this.rootDoc[this.panXFieldKey] = NumCast(this.rootDoc[this.panXFieldKey + '_reset']);
-        this.props.Document[this.panYFieldKey] = NumCast(this.rootDoc[this.panYFieldKey + '_reset']);
-        this.rootDoc[this.scaleFieldKey] = NumCast(this.rootDoc[this.scaleFieldKey + '_reset'], 1);
+        this.layoutDoc[this.panXFieldKey] = NumCast(this.dataDoc[this.panXFieldKey + '_reset']);
+        this.layoutDoc[this.panYFieldKey] = NumCast(this.dataDoc[this.panYFieldKey + '_reset']);
+        this.layoutDoc[this.scaleFieldKey] = NumCast(this.dataDoc[this.scaleFieldKey + '_reset'], 1);
     };
     ///
     /// resetView restores a freeform collection to unit scale and centered at (0,0)  UNLESS
@@ -1626,11 +1629,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     ///
     @undoBatch
     toggleResetView = () => {
-        this.rootDoc[this.autoResetFieldKey] = !this.rootDoc[this.autoResetFieldKey];
-        if (this.rootDoc[this.autoResetFieldKey]) {
-            this.rootDoc[this.panXFieldKey + '_reset'] = this.rootDoc[this.panXFieldKey];
-            this.rootDoc[this.panYFieldKey + '_reset'] = this.rootDoc[this.panYFieldKey];
-            this.rootDoc[this.scaleFieldKey + '_reset'] = this.rootDoc[this.scaleFieldKey];
+        this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey];
+        if (this.dataDoc[this.autoResetFieldKey]) {
+            this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey];
+            this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey];
+            this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey];
         }
     };
 
@@ -1639,8 +1642,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         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',
@@ -1652,11 +1655,11 @@ 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: () => this.props.pinToPres(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.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.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;
@@ -1681,7 +1684,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @undoBatch
     @action
     transcribeStrokes = () => {
-        if (this.props.Document._isGroup && 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;
@@ -1697,9 +1700,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     @action
     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 (visited.has(this.Document)) return;
+        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 selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
@@ -1708,14 +1711,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to
         activeDocs
-            .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))
+            .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.screenToLocalXf.inverse();
         snappableDocs
-            .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
+            .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
             .forEach(doc => {
                 const { left, top, width, height } = docDims(doc);
                 const topLeftInScreen = invXf.transformPoint(left, top);
@@ -1783,7 +1786,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.incrementalRender();
         return (
             <CollectionFreeFormPannableContents
-                rootDoc={this.rootDoc}
+                Document={this.Document}
                 brushedView={this.brushedView}
                 isAnnotationOverlay={this.isAnnotationOverlay}
                 transform={this.PanZoomCenterXf}
@@ -1801,7 +1804,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             <MarqueeView
                 {...this.props}
                 ref={this._marqueeViewRef}
-                ungroup={this.rootDoc._isGroup ? this.promoteCollection : undefined}
+                ungroup={this.Document.isGroup ? this.promoteCollection : undefined}
                 nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge}
                 addDocTab={this.addDocTab}
                 slowLoadDocuments={this.slowLoadDocuments}
@@ -1828,7 +1831,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const nh = this.nativeHeight;
         const hscale = nh ? this.props.PanelHeight() / nh : 1;
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
-        return wscale < hscale || this.layoutDoc.layout_fitWidth ? wscale : hscale;
+        return wscale < hscale || (this.props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale;
     }
     nativeDim = () => this.nativeDimScaling;
 
@@ -1849,7 +1852,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
     lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
     onPassiveWheel = (e: WheelEvent) => {
-        const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+        const docHeight = NumCast(this.rootDoc[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();
     };
@@ -1930,14 +1933,14 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
     dv &&
         DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
             if (!focused) {
-                const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+                const selfFfview = !dv.Document.isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
                 let containers = dv.props.docViewPath();
                 let parFfview = dv.CollectionFreeFormView;
                 for (var cont of containers) {
                     parFfview = parFfview ?? cont.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
+                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);
                 Doc.linkFollowHighlight(dv?.props.Document, false);
             }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 2092ecb7a..18b9b4423 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -348,6 +348,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             ? creator(selected, { title: 'nested stack' })
             : ((doc: Doc) => {
                   Doc.GetProto(doc).data = new List<Doc>(selected);
+                  Doc.GetProto(doc).isGroup = makeGroup;
                   Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
                   doc._freeform_panX = doc._freeform_panY = 0;
                   return doc;
@@ -355,7 +356,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         newCollection.isSystem = undefined;
         newCollection._width = this.Bounds.width;
         newCollection._height = this.Bounds.height;
-        newCollection._isGroup = makeGroup;
         newCollection._dragWhenActive = makeGroup;
         newCollection.x = this.Bounds.left;
         newCollection.y = this.Bounds.top;
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 274012000..be806d3cc 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -174,7 +174,7 @@ export class CollectionGridView extends CollectionSubView() {
     }
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
-    isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined);
+    isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined);
     /**
      *
      * @param layout
@@ -187,6 +187,7 @@ export class CollectionGridView extends CollectionSubView() {
         return (
             <DocumentView
                 {...this.props}
+                shouldNotScale={returnFalse}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 setContentView={emptyFunction}
@@ -199,7 +200,7 @@ export class CollectionGridView extends CollectionSubView() {
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 onClick={this.onChildClickHandler}
                 renderDepth={this.props.renderDepth + 1}
-                dontCenter={this.props.Document.centerY ? undefined : 'y'}
+                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
     }
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 4267a9059..a06ed3a2d 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) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0),
+            () => 5 + NumCast(this.dataDoc.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 }
         );
@@ -58,7 +58,7 @@ export class CollectionLinearView extends CollectionSubView() {
         if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
     };
 
-    dimension = () => NumCast(this.rootDoc._height);
+    dimension = () => NumCast(this.layoutDoc._height);
     getTransform = (ele: Opt<HTMLDivElement>) => {
         if (!ele) return Transform.Identity();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
@@ -183,7 +183,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     PanelWidth={doc[Width]}
                     PanelHeight={nested || doc._height ? doc[Height] : this.dimension}
                     renderDepth={this.props.renderDepth + 1}
-                    dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)}
+                    dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
                     styleProvider={this.props.styleProvider}
                     docViewPath={returnEmptyDoclist}
@@ -215,7 +215,7 @@ export class CollectionLinearView extends CollectionSubView() {
                 toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)}
                 onClick={() => {
                     this.layoutDoc.linearView_IsOpen = !isExpanded;
-                    ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.rootDoc }, console.log);
+                    ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log);
                 }}
                 tooltip={isExpanded ? 'Close' : 'Open'}
                 fillWidth={true}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index cb0d5e03f..4e2968933 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -17,7 +17,7 @@
         }
 
         .contentFittingDocumentView {
-            width: unset;
+            margin: auto;
         }
 
         .label-wrapper {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index c2586fb4b..05ec0448b 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -219,8 +219,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                             if (this.childDocs.includes(d)) {
                                 if (dropInd > this.childDocs.indexOf(d)) dropInd--;
                             }
-                            Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
-                            Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+                            Doc.RemoveDocFromList(this.dataDoc, this.props.fieldKey, d);
+                            Doc.AddDocToList(this.dataDoc, this.props.fieldKey, d, DocListCast(this.dataDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
                     })
                 );
@@ -235,18 +235,25 @@ export class CollectionMulticolumnView extends CollectionSubView() {
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
     isChildContentActive = () => {
-        const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+        const childDocsActive = this.props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
         return this.props.isContentActive?.() === false || childDocsActive === false
             ? false //
             : this.props.isDocumentActive?.() && childDocsActive
             ? true
             : undefined;
     };
-    getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
+    getDisplayDoc = (childLayout: Doc) => {
+        const width = () => this.lookupPixels(childLayout);
+        const height = () => this.props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+        const dxf = () =>
+            this.lookupIndividualTransform(childLayout)
+                .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
+                .scale(this.props.NativeDimScaling?.() || 1);
+        const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
         return (
             <DocumentView
-                Document={layout}
-                DataDoc={layout.resolvedDataDoc as Doc}
+                Document={childLayout}
+                DataDoc={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this.props.styleProvider}
                 docViewPath={this.props.docViewPath}
                 LayoutTemplate={this.props.childLayoutTemplate}
@@ -263,7 +270,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={this.props.childHideResizeHandles?.()}
+                hideResizeHandles={childLayout.layout_fitWidth || this.props.childHideResizeHandles?.() ? true : false}
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 fitContentsToBox={this.props.fitContentsToBox}
                 focus={this.props.focus}
@@ -278,6 +285,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 bringToFront={returnFalse}
+                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
     };
@@ -288,26 +296,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     @computed
     private get contents(): JSX.Element[] | null {
         const { childLayoutPairs } = this;
-        const { Document, PanelHeight } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
-            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_reflowHorizontal ? width() : Math.min(height() * aspect, width()));
-            const docheight = () => Math.min(docwidth() / aspect, height());
-            const dxf = () =>
-                this.lookupIndividualTransform(layout)
-                    .translate(-NumCast(Document._xMargin) - (width() - docwidth()) / 2, -NumCast(Document._yMargin))
-                    .scale(this.props.NativeDimScaling?.() || 1);
-            const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
             collector.push(
                 <Tooltip title={'Tab: ' + StrCast(layout.title)}>
-                    <div className="document-wrapper" key={'wrapper' + i} style={{ width: width() }}>
-                        {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
+                    <div className="document-wrapper" key={'wrapper' + i} style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
+                        {this.getDisplayDoc(layout)}
                         <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this.props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
-                        <WidthLabel layout={layout} collectionDoc={Document} />
+                        <WidthLabel layout={layout} collectionDoc={this.Document} />
                     </div>
                 </Tooltip>,
                 <ResizeBar
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index ec7200a03..5b0e6c981 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -10,11 +10,6 @@
         flex-direction: row;
         height: 100%;
         align-items: center;
-        margin: auto;
-
-        .contentFittingDocumentView {
-            height: unset;
-        }
 
         .label-wrapper {
             display: flex;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 249c3551b..b4b62e635 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -215,8 +215,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                             if (this.childDocs.includes(d)) {
                                 if (dropInd > this.childDocs.indexOf(d)) dropInd--;
                             }
-                            Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d);
-                            Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+                            Doc.RemoveDocFromList(this.dataDoc, this.props.fieldKey, d);
+                            Doc.AddDocToList(this.dataDoc, this.props.fieldKey, d, DocListCast(this.dataDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
                     })
                 );
@@ -231,14 +231,21 @@ export class CollectionMultirowView extends CollectionSubView() {
 
     isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
     isChildContentActive = () => {
-        const childDocsActive = this.props.childDocumentsActive?.() ?? this.rootDoc.childDocumentsActive;
+        const childDocsActive = this.props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
         return this.props.isContentActive?.() === false || childDocsActive === false
             ? false //
             : this.props.isDocumentActive?.() && childDocsActive
             ? true
             : undefined;
     };
-    getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number, shouldNotScale: () => boolean) => {
+    getDisplayDoc = (layout: Doc) => {
+        const height = () => this.lookupPixels(layout);
+        const width = () => this.props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+        const dxf = () =>
+            this.lookupIndividualTransform(layout)
+                .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
+                .scale(this.props.NativeDimScaling?.() || 1);
+        const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={layout}
@@ -252,13 +259,13 @@ export class CollectionMultirowView extends CollectionSubView() {
                 PanelHeight={height}
                 shouldNotScale={shouldNotScale}
                 rootSelected={this.rootSelected}
-                dropAction={StrCast(this.rootDoc.childDragAction) as dropActionType}
+                dropAction={StrCast(this.Document.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={this.props.childHideResizeHandles?.()}
+                hideResizeHandles={layout.layout_fitWidth || this.props.childHideResizeHandles?.() ? true : false}
                 hideDecorationTitle={this.props.childHideDecorationTitle?.()}
                 fitContentsToBox={this.props.fitContentsToBox}
                 dragAction={this.props.childDragAction}
@@ -274,6 +281,7 @@ export class CollectionMultirowView extends CollectionSubView() {
                 addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 bringToFront={returnFalse}
+                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
         );
     };
@@ -284,24 +292,13 @@ export class CollectionMultirowView extends CollectionSubView() {
     @computed
     private get contents(): JSX.Element[] | null {
         const { childLayoutPairs } = this;
-        const { Document, PanelWidth } = this.props;
         const collector: JSX.Element[] = [];
         for (let i = 0; i < childLayoutPairs.length; i++) {
             const { layout } = childLayoutPairs[i];
-            const aspect = Doc.NativeAspect(layout, undefined, true);
-            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_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)
-                    .scale(this.props.NativeDimScaling?.() || 1);
-            const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
             collector.push(
-                <div className="document-wrapper" style={{ height: height() }} key={'wrapper' + i}>
-                    {this.getDisplayDoc(layout, dxf, docwidth, docheight, shouldNotScale)}
-                    <HeightLabel layout={layout} collectionDoc={Document} />
+                <div className="document-wrapper" style={{ flexDirection: 'row', height: this.lookupPixels(layout) }} key={'wrapper' + i}>
+                    {this.getDisplayDoc(layout)}
+                    <HeightLabel layout={layout} collectionDoc={this.Document} />
                 </div>,
                 <ResizeBar
                     height={resizerHeight}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 190e4ff2a..22b80e21d 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -85,12 +85,12 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     @computed get _selectedDocs() {
-        const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.rootDoc));
+        const selected = SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document));
         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);
+                if (contextPath.includes(this.Document)) {
+                    const parentInd = contextPath.indexOf(this.Document);
                     return parentInd < contextPath.length - 1 ? [contextPath[parentInd + 1]] : [];
                 }
             }
@@ -146,7 +146,7 @@ export class CollectionSchemaView extends CollectionSubView() {
 
         Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
         this._keysDisposer = observe(
-            this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+            this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>,
             change => {
                 switch (change.type as any) {
                     case 'splice':
@@ -154,7 +154,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                         (change as any).added.forEach((doc: Doc) => // for each document added 
                             Doc.GetAllPrototypes(doc.value as Doc).forEach(proto => // for all of its prototypes (and itself)
                                 Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
-                                    !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(''))))));
+                                    !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(key, key === 'author'))))));
                         break;
                     case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
                 }
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 9d5b533d1..2c251d3cf 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -102,7 +102,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
     }
 
     @computed get defaultCellContent() {
-        const { color, textDecoration, fieldProps } = SchemaTableCell.renderProps(this.props);
+        const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this.props);
 
         return (
             <div
@@ -111,6 +111,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                     color,
                     textDecoration,
                     width: '100%',
+                    pointerEvents,
                 }}>
                 <EditableView
                     oneLine={this.props.oneLine}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3af523d73..fda3f731d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -189,13 +189,13 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
     const editorView = textView?.EditorView;
     // prettier-ignore
     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):
+        [ where, {      checkResult: () =>(editorView ? (where === 'vcent' ?  RichTextMenu.Instance.textVcenter:
                                                                               (RichTextMenu.Instance.textAlign === where)): 
-                                                         where === 'vcent' ? BoolCast(Doc.UserDoc().layout_centered):
+                                                         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):
+                        toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
                                                                               RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
-                                                            where === 'vcent' ?  Doc.UserDoc().layout_centered = !Doc.UserDoc().layout_centered:
+                                                            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 =>
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8d80f1364..807a77233 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -366,8 +366,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
                 const textField = Doc.LayoutFieldKey(newDoc);
                 Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
-                Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
-                Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
+                Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+                Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
                 if (Doc.IsInMyOverlay(this.rootDoc)) {
                     newDoc.overlayX = this.rootDoc.x;
                     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index dd16ab71b..5b4fb3e29 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,6 +1,6 @@
 import { action, computed, observable } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
@@ -15,14 +15,16 @@ import { StyleProp } from '../StyleProvider';
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import React = require('react');
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
     x: number;
     y: number;
     z: number;
-    width: number; // -1 means use PanelWidth which should be the same as the Document's width, but avoids the delay of waiting for the width prop to change in PanelWidth function
-    height: number; // -1 means use PanelHeight
+    width: number;
+    height: number;
     zIndex?: number;
+    autoDim?: number; // 1 means use Panel Width/Height, 0 means use width/height
     rotation?: number;
     color?: string;
     backgroundColor?: string;
@@ -72,8 +74,8 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     w_Transition = ()      => this.Transition; // prettier-ignore
     w_DataTransition = ()  => this.DataTransition; // prettier-ignore
 
-    PanelWidth = ()        => this.Width  > 0 ? this.Width : this.props.PanelWidth?.(); // prettier-ignore
-    PanelHeight = ()       => this.Height > 0 ? this.Height : this.props.PanelHeight?.(); // 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
     componentDidUpdate() {
         this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower]));
@@ -113,7 +115,7 @@ export interface CollectionFreeFormDocumentViewProps {
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
     get displayName() { // this makes mobx trace() statements more descriptive
-        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
+        return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
     public static animFields: { key: string; val?: number }[] = [
         { key: 'x' },
@@ -148,14 +150,14 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
 
     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);
+            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);
+            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> });
     }
@@ -163,40 +165,52 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}-indexed`], listSpec('string'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('string'), []).slice();
             findexed[timecode] = vals[val] as any as string;
-            d[`${val}-indexed`] = new List<string>(findexed);
+            d[`${val}_indexed`] = new List<string>(findexed);
         });
     }
 
     public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
         const timecode = Math.round(time);
         Object.keys(vals).forEach(val => {
-            const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice();
+            const findexed = Cast(d[`${val}_indexed`], listSpec('number'), []).slice();
             findexed[timecode] = vals[val] as any as number;
-            d[`${val}-indexed`] = new List<number>(findexed);
+            d[`${val}_indexed`] = new List<number>(findexed);
         });
     }
+    public static gotoKeyFrame(doc: Doc, newFrame: number) {
+        if (doc) {
+            const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+            const currentFrame = Cast(doc._currentFrame, 'number', null);
+            if (currentFrame === undefined) {
+                doc._currentFrame = 0;
+                CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0);
+            }
+            CollectionFreeFormView.updateKeyframe(undefined, [...childDocs, doc], currentFrame || 0);
+            doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
+        }
+    }
 
     public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
         docs.forEach(doc => {
             if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
-            if (!doc['opacity-indexed']) {
+            if (!doc['opacity_indexed']) {
                 // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
-                doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
+                doc['opacity_indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
             }
             CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val)));
             CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
             CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode)));
             const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so
             //doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
-            targetDoc.activeFrame = ComputedField.MakeFunction('self.embedContainer?._currentFrame||0');
+            targetDoc.activeFrame = ComputedField.MakeFunction('this.embedContainer?._currentFrame||0');
             targetDoc.dataTransition = 'inherit';
         });
     }
 
     @action public float = () => {
-        const topDoc = this.rootDoc;
+        const topDoc = this.Document;
         const containerDocView = this.props.docViewPath().lastElement();
         const screenXf = containerDocView?.screenToLocalTransform();
         if (screenXf) {
@@ -238,7 +252,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     //  undefined - this is not activated by a group
     isGroupActive = () => {
         if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
-        const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
+        const isGroup = this.Document.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
     };
     public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
@@ -265,3 +279,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         );
     }
 }
+ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) {
+    CollectionFreeFormDocumentView.gotoKeyFrame(doc, newFrame);
+});
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 8c8c987fe..eed787b6d 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, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate } from '../../../fields/DocSymbols';
@@ -66,7 +66,6 @@ export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
 
 interface HTMLtagProps {
     Document: Doc;
-    RootDoc: Doc;
     htmltag: string;
     onClick?: ScriptField;
     onInput?: ScriptField;
@@ -90,18 +89,18 @@ interface HTMLtagProps {
 export class HTMLtag extends React.Component<HTMLtagProps> {
     click = (e: React.MouseEvent) => {
         const clickScript = (this.props as any).onClick as Opt<ScriptField>;
-        clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling });
+        clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling });
     };
     onInput = (e: React.FormEvent<HTMLDivElement>) => {
         const onInputScript = (this.props as any).onInput as Opt<ScriptField>;
-        onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent });
+        onInputScript?.script.run({ this: this.props.Document, self: this.props.Document, value: (e.target as any).textContent });
     };
     render() {
         const style: { [key: string]: any } = {};
-        const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit;
+        const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', '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) || '';
+            // bcz: this executes a script to convert a property expression string:  { script }  into a value
+            return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || '';
         };
         Object.keys(divKeys).map((prop: string) => {
             const p = (this.props as any)[prop] as string;
@@ -119,9 +118,6 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 @observer
 export class DocumentContentsView extends React.Component<
     DocumentViewProps & {
-        isSelected: () => boolean;
-        select: (ctrl: boolean) => void;
-        NativeDimScaling?: () => number;
         setHeight?: (height: number) => void;
         layout_fieldKey: string;
     }
@@ -137,6 +133,10 @@ export class DocumentContentsView extends React.Component<
         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 dataDoc() {
         const proto = this.props.DataDoc || Doc.GetProto(this.props.Document);
         return proto instanceof Promise ? undefined : proto;
@@ -170,7 +170,6 @@ export class DocumentContentsView extends React.Component<
         ];
         const list = {
             ...OmitKeys(this.props, [...docOnlyProps], '').omit,
-            RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc,
             Document: this.layoutDoc,
             DataDoc: this.dataDoc,
             onClick: onClick,
@@ -195,7 +194,7 @@ export class DocumentContentsView extends React.Component<
 
         // 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 RootDoc={props.RootDoc} 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);
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ef2736b6d..f89768d3d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -283,7 +283,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return Doc.LayoutFieldKey(this.layoutDoc);
     }
     @computed get layout_showTitle() {
-        return this.props.styleProvider?.(this.rootDoc, 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;
@@ -302,13 +302,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     @computed get widgetDecorations() {
         TraceMobx();
-        return this.props.styleProvider?.(this.rootDoc, 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');
     }
     @computed get docContents() {
-        return this.props.styleProvider?.(this.rootDoc, 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;
@@ -369,7 +369,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 // 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'
                     ? false
-                    : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
+                    : Doc.ActiveTool !== InkTool.None || SnappingManager.GetCanEmbed() || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()
                     ? true
                     : undefined;
             },
@@ -405,8 +405,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     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.rootDoc === this.Document) ? views : [this.props.DocumentView()];
-            const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.rootDoc));
+            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()
@@ -417,7 +417,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
             dragData.draggedViews = [this.props.DocumentView()];
-            dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false;
+            dragData.canEmbed = this.Document.dragAction ?? this.props.dragAction ? true : false;
             DragManager.StartDocumentDrag(
                 selected.map(dv => dv.docView!._mainCont.current!),
                 dragData,
@@ -430,8 +430,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
         const targetMatch =
-            Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document
-            (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
+            Doc.AreProtosEqual(anchor, this.Document) || // anchor is this document, so anchor's properties apply to this document
+            (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.Document)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
                 ? true
                 : false;
         return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
@@ -450,23 +450,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         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.rootDoc._keepZWhenDragged && this.props.bringToFront(this.rootDoc);
+            !this.layoutDoc._keepZWhenDragged && this.props.bringToFront(this.Document);
             if (this._doubleTap) {
                 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.layoutDoc,
-                            self: this.rootDoc,
+                            this: this.Document,
                             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');
-                } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
-                    UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
+                } 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);
                 } else {
@@ -488,8 +487,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
                         this.onClickHandler?.script.run(
                             {
-                                this: this.layoutDoc,
-                                self: this.rootDoc,
+                                this: this.Document,
                                 _readOnly_: false,
                                 scriptContext: this.props.scriptContext,
                                 documentView: this.props.DocumentView(),
@@ -505,7 +503,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             : '';
                         DocumentViewInternal.addDocTabFunc = oldFunc;
                     };
-                    clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title);
+                    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.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -516,7 +514,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 const sendToBack = e.altKey;
                 this._singleClickFunc =
                     // prettier-ignore
-                    clickFunc ?? (() => (sendToBack ? this.props.DocumentView().props.bringToFront(this.rootDoc, 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;
@@ -538,7 +536,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (this.props.isGroupActive?.() === 'child' && !this.props.isDocumentActive?.()) return;
         this._longPressSelector = setTimeout(() => {
             if (DocumentView.LongPress) {
-                if (this.rootDoc.undoIgnoreFields) {
+                if (this.Document.undoIgnoreFields) {
                     runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline));
                 } else {
                     this.props.select(false);
@@ -563,10 +561,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             ) {
                 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.rootDoc.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.rootDoc._dragWhenActive, this.props.dragWhenActive))) {
+                if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this.props.dragWhenActive))) {
                     document.addEventListener('pointermove', this.onPointerMove);
                 }
             }
@@ -596,9 +594,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this._longPressSelector && clearTimeout(this._longPressSelector);
 
         if (this.onPointerUpHandler?.script) {
-            this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
+            this.onPointerUpHandler.script.run({ this: this.Document }, console.log);
         } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
-            this._doubleTap = (this.onDoubleClickHandler?.script || this.rootDoc.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
+            this._doubleTap = (this.onDoubleClickHandler?.script || this.Document.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME;
             if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected
         }
         if (DocumentView.LongPress) e.preventDefault();
@@ -607,7 +605,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch
     @action
     toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
-        const hadOnClick = this.rootDoc.onClick;
+        const hadOnClick = this.Document.onClick;
         this.noOnClick();
         this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
         this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
@@ -653,11 +651,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
         if (linkdrag) {
             linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
-            if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.rootDoc) {
+            if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.Document) {
                 if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
                     de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
                 }
-                if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.embedContainer) {
+                if (de.complete.annoDragData || this.Document !== linkdrag.linkSourceDoc.embedContainer) {
                     const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc;
                     de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
                     if (de.complete.linkDocument) {
@@ -713,7 +711,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
-        if (e && this.rootDoc._layout_hideContextMenu && Doc.noviceMode) {
+        if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
             e.preventDefault();
             e.stopPropagation();
             //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
@@ -738,7 +736,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() && 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')) {
@@ -751,11 +749,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
         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.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), 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.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), 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);
@@ -765,22 +761,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             if (this.props.renderDepth === 0) {
                 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' });
+            this.Document.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: '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)) {
+            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) {
                     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.rootDoc, false)), icon: 'arrow-up' });
-                    zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, 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.rootDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
-                        event: undoBatch(action(() => (this.rootDoc._keepZWhenDragged = !this.rootDoc._keepZWhenDragged))),
+                        description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
+                        event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
                         icon: 'hand-point-up',
                     });
                     !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
@@ -816,7 +812,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
             const more = cm.findByDescription('More...');
             const moreItems = more && 'subitems' in more ? more.subitems : [];
-            if (!Doc.IsSystem(this.rootDoc)) {
+            if (!Doc.IsSystem(this.Document)) {
                 if (!Doc.noviceMode) {
                     moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' });
@@ -833,9 +829,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
         }
         const constantItems: ContextMenuProps[] = [];
-        if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._type_collection !== CollectionViewType.Docking) {
+        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.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' });
+            (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' });
@@ -847,8 +843,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         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: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
-        !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DocData]), icon: 'hand-point-right' });
+        !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;
@@ -902,7 +898,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @computed get _rootSelected() {
-        return this.props.isSelected() || (this.props.Document.rootDocument && this.props.rootSelected?.()) || false;
+        return this.props.isSelected() || BoolCast(this.Document.rootDocument && this.props.rootSelected?.());
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
@@ -931,7 +927,7 @@ 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.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
+        const noBackground = this.Document.isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
         return (
             <div
                 className="documentView-contentsView"
@@ -946,7 +942,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     docViewPath={this.props.viewPath}
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
-                    NativeDimScaling={this.props.NativeDimScaling}
                     PanelHeight={this.panelHeight}
                     setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
                     isContentActive={this.isContentActive}
@@ -985,17 +980,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
     @computed get directLinks() {
         TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(
+        return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(
             link =>
-                (link.link_matchEmbeddings ? link.link_anchor_1 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc)) ||
-                (link.link_matchEmbeddings ? link.link_anchor_2 === this.rootDoc : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc)) ||
-                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) ||
-                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc))
+                (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
+                (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
+                ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
+                ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
         );
     }
     @computed get allLinks() {
         TraceMobx();
-        return LinkManager.Instance.getAllRelatedLinks(this.rootDoc);
+        return LinkManager.Instance.getAllRelatedLinks(this.Document);
     }
     hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
     @computed get allLinkEndpoints() {
@@ -1020,7 +1015,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     removeDocument={this.hideLink(link)}
                     styleProvider={this.anchorStyleProvider}
                     LayoutTemplate={undefined}
-                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)}
+                    LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
                 />
             </div>
         ));
@@ -1143,7 +1138,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             <div
                 className="documentView-captionWrapper"
                 style={{
-                    pointerEvents: this.rootDoc.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')),
                 }}>
@@ -1191,8 +1186,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                           dropdownWidth,
                           StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
                           action((field: string | number) => {
-                              if (this.rootDoc.layout_showTitle) {
-                                  this.rootDoc._layout_showTitle = field;
+                              if (this.layoutDoc.layout_showTitle) {
+                                  this.layoutDoc._layout_showTitle = field;
                               } else if (!this.props.layout_showTitle) {
                                   Doc.UserDoc().layout_showTitle = field;
                               }
@@ -1216,11 +1211,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         display="block"
                         oneLine={true}
                         fontSize={(this.titleHeight / 15) * 10}
-                        GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.rootDoc, showTitle.split(';')[0]))}
+                        GetValue={() => (showTitle.split(';').length !== 1 ? '#' + showTitle : Field.toKeyValueString(this.Document, showTitle.split(';')[0]))}
                         SetValue={undoBatch((input: string) => {
                             if (input?.startsWith('#')) {
-                                if (this.rootDoc.layout_showTitle) {
-                                    this.rootDoc._layout_showTitle = input?.substring(1);
+                                if (this.layoutDoc.layout_showTitle) {
+                                    this.layoutDoc._layout_showTitle = input?.substring(1);
                                 } else if (!this.props.layout_showTitle) {
                                     Doc.UserDoc().layout_showTitle = input?.substring(1) ?? 'author_date';
                                 }
@@ -1246,7 +1241,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     renderDoc = (style: object) => {
         TraceMobx();
-        return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DocData]) === AclPrivate
+        return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate
             ? null
             : this.docContents ?? (
                   <div
@@ -1299,10 +1294,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
     @computed get highlighting() {
-        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
     }
     @computed get borderPath() {
-        return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BorderPath);
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderPath);
     }
     render() {
         TraceMobx();
@@ -1313,7 +1308,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 ? this.boxShadow
                 : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
                 ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
-                : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+                : 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',
@@ -1337,7 +1332,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
                 }}>
                 <>
-                    {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)}
+                    {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
                     {borderPath?.jsx}
                 </>
             </div>
@@ -1377,7 +1372,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     private _disposers: { [name: string]: IReactionDisposer } = {};
     public clearViewTransition = () => {
         this.ViewTimer && clearTimeout(this.ViewTimer);
-        this.rootDoc._viewTransition = undefined;
+        this.layoutDoc._viewTransition = undefined;
     };
     public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
 
@@ -1385,15 +1380,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
         this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
-        this.rootDoc[Animation] = presEffect;
-        this.AnimEffectTimer = setTimeout(() => (this.rootDoc[Animation] = undefined), timeInMs);
+        this.Document[Animation] = presEffect;
+        this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
     };
     public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
-        this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`;
+        this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
         if (dataTrans) this.rootDoc._dataTransition = `${transProp} ${timeInMs}ms`;
         this.ViewTimer && clearTimeout(this.ViewTimer);
         return (this.ViewTimer = setTimeout(() => {
-            this.rootDoc._viewTransition = undefined;
+            this.layoutDoc._viewTransition = undefined;
             this.rootDoc._dataTransition = 'inherit';
             afterTrans?.();
         }, timeInMs + 10));
@@ -1421,7 +1416,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         DocServer.GetRefField(docId).then(docx =>
             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)
+                Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
             )
         );
     }
@@ -1445,16 +1440,16 @@ 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.rootDoc || 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.rootDoc);
     }
     get LayoutFieldKey() {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     @computed get layout_fitWidth() {
-        return this.docView?._componentView?.layout_fitWidth?.() ?? this.props.layout_fitWidth?.(this.rootDoc) ?? this.layoutDoc?.layout_fitWidth;
+        return this.docView?._componentView?.layout_fitWidth?.() ?? this.props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
     }
     @computed get anchorViewDoc() {
-        return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.rootDoc['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.rootDoc['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.isSelected() ? ':selected' : ''));
@@ -1526,7 +1521,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         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 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())) {
             return undefined;
@@ -1560,7 +1555,7 @@ 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.rootDoc);
+            this.props.bringToFront(this.Document);
         }
     }
     @undoBatch
@@ -1574,8 +1569,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
         setTimeout(
             action(() => {
-                if (useExistingLayout && custom && this.rootDoc['layout_' + view]) {
-                    this.rootDoc.layout_fieldKey = 'layout_' + view;
+                if (useExistingLayout && custom && this.Document['layout_' + view]) {
+                    this.Document.layout_fieldKey = 'layout_' + view;
                 } else {
                     this.setCustomView(custom, view);
                 }
@@ -1592,6 +1587,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;
     docViewPathFunc = () => this.docViewPath;
     isSelected = () => this.SELECTED;
@@ -1600,7 +1596,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         else {
             SelectionManager.SelectView(this, extendSelection);
             if (focusSelection) {
-                DocumentManager.Instance.showDocument(this.rootDoc, {
+                DocumentManager.Instance.showDocument(this.Document, {
                     willZoomCentered: true,
                     zoomScale: 0.9,
                     zoomTime: 500,
@@ -1608,6 +1604,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
             }
         }
     };
+    ShouldNotScale = () => this.shouldNotScale;
     NativeWidth = () => this.effectiveNativeWidth;
     NativeHeight = () => this.effectiveNativeHeight;
     PanelWidth = () => this.panelWidth;
@@ -1622,8 +1619,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     @action
     componentDidMount() {
-        this.rootDoc[DocViews].add(this);
-        this._disposers.updateContentsScript = reaction(() => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.rootDoc, self: this.rootDoc }).result, emptyFunction);
+        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
             () => NumCast(this.layoutDoc._height),
@@ -1636,7 +1633,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     @action
     componentWillUnmount() {
-        this.rootDoc[DocViews].delete(this);
+        this.Document[DocViews].delete(this);
         Object.values(this._disposers).forEach(disposer => disposer?.());
         !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
     }
@@ -1662,7 +1659,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             <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
+                        this.Document
                     )}
                 </div>
             </div>
@@ -1683,15 +1680,14 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         style={{
                             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
-                                ? undefined
-                                : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+                            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}
                             DocumentView={this.selfView}
                             viewPath={this.docViewPathFunc}
+                            shouldNotScale={this.ShouldNotScale}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
@@ -1699,6 +1695,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             NativeDimScaling={this.NativeDimScaling}
                             isSelected={this.isSelected}
                             select={this.select}
+                            layout_fitWidth={this.layout_fitWidthFunc}
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             focus={this.props.focus || emptyFunction}
                             ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
@@ -1719,7 +1716,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
 });
 
 ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
-    LightboxView.Instance.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0);
+    LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
 });
 
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index de5ad1631..ba5370360 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -26,7 +26,6 @@ export enum ButtonType {
     TextButton = 'textBtn',
     MenuButton = 'menuBtn',
     DropdownList = 'dropdownList',
-    DropdownButton = 'dropdownBtn',
     ClickButton = 'clickBtn',
     ToggleButton = 'toggleBtn',
     ColorButton = 'colorBtn',
@@ -78,7 +77,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
 
     // Determining UI Specs
     @computed get label() {
-        return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
+        return StrCast(this.dataDoc.icon_label, StrCast(this.Document.title));
     }
     Icon = (color: string, iconFalse?: boolean) => {
         let icon;
@@ -91,13 +90,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
     };
     @computed get dropdown() {
-        return BoolCast(this.rootDoc.dropDownOpen);
+        return BoolCast(this.Document.dropDownOpen);
     }
     @computed get buttonList() {
-        return StrListCast(this.rootDoc.btnList);
+        return StrListCast(this.Document.btnList);
     }
     @computed get type() {
-        return StrCast(this.rootDoc.btnType);
+        return StrCast(this.Document.btnType);
     }
 
     /**
@@ -131,8 +130,8 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 type = 'slider';
                 break;
         }
-        const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined });
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
+        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)));
 
@@ -144,52 +143,21 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 showPlusMinus={false}
                 tooltip={this.label}
                 type={Type.PRIM}
-                min={NumCast(this.rootDoc.numBtnMin, 0)}
-                max={NumCast(this.rootDoc.numBtnMax, 100)}
+                min={NumCast(this.dataDoc.numBtnMin, 0)}
+                max={NumCast(this.dataDoc.numBtnMax, 100)}
                 number={checkResult}
-                setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}
+                setNumber={undoable(value => numScript(value), `${this.Document.title} button set from list`)}
                 fillWidth
             />
         );
     }
 
-    /**
-     * Dropdown button
-     */
-    @computed get dropdownButton() {
-        const active: string = StrCast(this.rootDoc.dropDownOpen);
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        return (
-            <div
-                className={`menuButton ${this.type} ${active}`}
-                style={{ color, backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
-                onClick={action(() => {
-                    this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
-                    this.noTooltip = this.rootDoc.dropDownOpen;
-                    Doc.UnBrushAllDocs();
-                })}>
-                {this.Icon(color)}
-                {!this.label || !FontIconBox.ShowIconLabels ? null : (
-                    <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
-                        {' '}
-                        {this.label}{' '}
-                    </div>
-                )}
-                <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
-                    <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
-                </div>
-                {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null}
-            </div>
-        );
-    }
-
     dropdownItemDown = (e: React.PointerEvent, value: string | number) => {
         setupMoveUpEvents(
             this,
             e,
             (e: PointerEvent) => {
-                return ScriptCast(this.rootDoc.onDragScript)?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: { doc: value, e } }).result;
+                return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, value: { doc: value, e } }).result;
             },
             emptyFunction,
             emptyFunction
@@ -201,7 +169,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Dropdown list
      */
     @computed get dropdownListButton() {
-        const script = ScriptCast(this.rootDoc.script);
+        const script = ScriptCast(this.Document.script);
 
         let noviceList: string[] = [];
         let text: string | undefined;
@@ -237,7 +205,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
         } else {
-            text = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+            text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
             getStyle = (val: string) => ({ fontFamily: val });
         }
@@ -255,7 +223,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         return (
             <Dropdown
                 selectedVal={text}
-                setSelectedVal={undoable(value => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), `dropdown select ${this.label}`)}
+                setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)}
                 color={SettingsManager.userColor}
                 background={SettingsManager.userVariantColor}
                 type={Type.TERT}
@@ -270,7 +238,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     }
 
     @computed get colorScript() {
-        return ScriptCast(this.rootDoc.script);
+        return ScriptCast(this.Document.script);
     }
 
     colorBatch: UndoManager.Batch | undefined;
@@ -278,18 +246,18 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Color button
      */
     @computed get colorButton() {
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
-        const tooltip: string = StrCast(this.rootDoc.toolTip);
+        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const curColor = this.colorScript?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
+        const tooltip: string = StrCast(this.Document.toolTip);
 
         return (
             <ColorPicker
                 setSelectedColor={value => {
                     if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
-                    this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                 }}
                 setFinalColor={value => {
-                    this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                     this.colorBatch?.end();
                     this.colorBatch = undefined;
                 }}
@@ -306,13 +274,13 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     }
     @computed get multiToggleButton() {
         // Determine the type of toggle button
-        const tooltip: string = StrCast(this.rootDoc.toolTip);
+        const tooltip: string = StrCast(this.Document.toolTip);
 
-        const script = ScriptCast(this.rootDoc.onClick);
-        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        const script = ScriptCast(this.Document.onClick);
+        const toggleStatus = script ? script.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const items = DocListCast(this.rootDoc.data);
+        const color = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color);
+        const items = DocListCast(this.dataDoc.data);
         return (
             <MultiToggle
                 tooltip={`Toggle ${tooltip}`}
@@ -320,7 +288,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 color={color}
                 background={SettingsManager.userBackgroundColor}
                 label={this.label}
-                items={DocListCast(this.rootDoc.data).map(item => ({
+                items={DocListCast(this.dataDoc.data).map(item => ({
                     icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />,
                     tooltip: StrCast(item.toolTip),
                     val: StrCast(item.toolType),
@@ -336,14 +304,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
 
     @computed get toggleButton() {
         // Determine the type of toggle button
-        const buttonText: string = StrCast(this.rootDoc.buttonText);
-        const tooltip: string = StrCast(this.rootDoc.toolTip);
+        const buttonText = StrCast(this.dataDoc.buttonText);
+        const tooltip = StrCast(this.Document.toolTip);
 
-        const script = ScriptCast(this.rootDoc.onClick);
-        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        const script = ScriptCast(this.Document.onClick);
+        const toggleStatus = script ? script.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result : false;
         // Colors
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, 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
@@ -356,7 +324,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 //background={SettingsManager.userBackgroundColor}
                 icon={this.Icon(color)!}
                 label={this.label}
-                onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
+                onPointerDown={() => script.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false })}
             />
         );
     }
@@ -365,20 +333,20 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      * Default
      */
     @computed get defaultButton() {
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const tooltip: string = StrCast(this.rootDoc.toolTip);
+        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} />;
     }
 
     @computed get editableText() {
         // Script for running the toggle
-        const script = ScriptCast(this.rootDoc.script);
+        const script = ScriptCast(this.Document.script);
         // Function to run the script
-        const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result;
+        const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result;
 
-        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).result;
+        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, value, _readOnly_: false }).result;
 
         return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
 
@@ -386,16 +354,16 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             <div className="menuButton editableText">
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
                 <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
-                    <EditableView GetValue={() => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+                    <EditableView GetValue={() => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
                 </div>
             </div>
         );
     }
 
     render() {
-        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-        const tooltip = StrCast(this.rootDoc.toolTip);
-        const scriptFunc = () => ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false });
+        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 };
         // prettier-ignore
         switch (this.type) {
@@ -405,15 +373,14 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             case ButtonType.EditableText:       return this.editableText; 
             case ButtonType.DropdownList:       return this.dropdownListButton;
             case ButtonType.ColorButton:        return this.colorButton; 
-            case ButtonType.DropdownButton:     return this.dropdownButton;
             case ButtonType.MultiToggleButton:  return this.multiToggleButton;
             case ButtonType.ToggleButton:       return this.toggleButton;
             case ButtonType.ClickButton:
             case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
             case ButtonType.TextButton: return <Button     {...btnProps} color={color} 
-            background={SettingsManager.userBackgroundColor} text={StrCast(this.rootDoc.buttonText)}/>;
+                        background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
             case ButtonType.MenuButton: return <IconButton {...btnProps} color={color} 
-            background={SettingsManager.userBackgroundColor}  size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; 
+                        background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; 
         }
         return this.defaultButton;
     }
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 40f48dafe..daf6cd9a6 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -38,7 +38,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     componentDidMount() {
         this.props.setContentView?.(this);
         reaction(
-            () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.rootDoc.xRange, this.rootDoc.yRange],
+            () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
             () => this.createGraph()
         );
     }
@@ -64,8 +64,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
                 target: '#' + this._plotEle.id,
                 width,
                 height,
-                xAxis: { domain: Cast(this.rootDoc.xRange, listSpec('number'), [-10, 10]) },
-                yAxis: { domain: Cast(this.rootDoc.yRange, listSpec('number'), [-1, 9]) },
+                xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) },
+                yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) },
                 grid: true,
                 data: fns.map(fn => ({
                     fn,
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 103843046..19e393968 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -559,7 +559,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 {this.annotationLayer}
                 {!this._mainCont.current || !this._annotationLayer.current ? null : (
                     <MarqueeAnnotator
-                        rootDoc={this.rootDoc}
+                        Document={this.Document}
+                        ref={this._marqueeref}
                         scrollTop={0}
                         annotationLayerScrollTop={0}
                         scaling={returnOne}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 95aeb5331..95d2a2667 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -110,7 +110,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.buttons === 1 && this.props.isSelected(true)) {
+        if (e.buttons === 1 && this.props.isSelected()) {
             e.stopPropagation();
         }
     };
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 416cb11cc..30cd65cb4 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -22,12 +22,12 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     onClickScriptDisable = returnAlways;
     @computed get anchor1() {
-        const anchor1 = DocCast(this.rootDoc.link_anchor_1);
+        const anchor1 = DocCast(this.dataDoc.link_anchor_1);
         const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
         return DocumentManager.Instance.getDocumentView(anchor_1, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
     }
     @computed get anchor2() {
-        const anchor2 = DocCast(this.rootDoc.link_anchor_2);
+        const anchor2 = DocCast(this.dataDoc.link_anchor_2);
         const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
         return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement());
     }
@@ -106,22 +106,22 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined;
     render() {
         if (this.renderProps) {
-            const highlight = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Highlighting);
+            const highlight = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Highlighting);
             const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
 
             const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
             const text = bez.get(0.5);
-            const linkDesc = StrCast(this.rootDoc.link_description) || 'description';
-            const strokeWidth = NumCast(this.rootDoc.stroke_width, 4);
-            const dash = StrCast(this.rootDoc.stroke_dash);
+            const linkDesc = StrCast(this.dataDoc.link_description) || 'description';
+            const strokeWidth = NumCast(this.dataDoc.stroke_width, 4);
+            const dash = StrCast(this.Document.stroke_dash);
             const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
             const { pts, lx, ty, rx, by } = this.renderProps;
             return (
                 <div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
                     <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
                         <defs>
-                            <filter x="0" y="0" width="1" height="1" id={`${this.rootDoc[Id] + 'background'}`}>
-                                <feFlood floodColor={`${StrCast(this.rootDoc._backgroundColor, 'lightblue')}`} result="bg" />
+                            <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
+                                <feFlood floodColor={`${StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
                                 <feMerge>
                                     <feMergeNode in="bg" />
                                     <feMergeNode in="SourceGraphic" />
@@ -141,7 +141,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
                         />
                         <text
-                            filter={`url(#${this.rootDoc[Id] + 'background'})`}
+                            filter={`url(#${this.Document[Id] + 'background'})`}
                             style={{ pointerEvents: this.props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
                             x={text.x - lx}
                             y={text.y - ty}>
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 42bada0ef..56f0a49b8 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -15,17 +15,17 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(MapPushpinBox, fieldKey);
     }
     componentDidMount() {
-        this.mapBoxView.addPushpin(this.rootDoc);
+        this.mapBoxView.addPushpin(this.Document);
     }
     componentWillUnmount() {
-        this.mapBoxView.deletePushpin(this.rootDoc);
+        this.mapBoxView.deletePushpin(this.Document);
     }
 
     @computed get mapBoxView() {
         return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as MapBox;
     }
     @computed get mapBox() {
-        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+        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 108fa5ce5..1035116d5 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -578,7 +578,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     <PDFViewer
                         {...this.props}
                         sidebarAddDoc={this.sidebarAddDocument}
-                        rootDoc={this.rootDoc}
                         addDocTab={this.sidebarAddDocTab}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 116069cbd..3838ce4ee 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -62,7 +62,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     public static WorkspaceStopRecording() {
-        const remDoc = RecordingBox.screengrabber?.rootDoc;
+        const remDoc = RecordingBox.screengrabber?.Document;
         if (remDoc) {
             //if recordingbox is true; when we press the stop button. changed vals temporarily to see if changes happening
             RecordingBox.screengrabber?.Pause?.();
@@ -122,7 +122,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
         value.overlayY = window.innerHeight - 180;
         Doc.AddToMyOverlay(value);
         DocumentManager.Instance.AddViewRenderedCb(value, docView => {
-            Doc.UserDoc().currentRecording = docView.rootDoc;
+            Doc.UserDoc().currentRecording = docView.Document;
             docView.select(false);
             RecordingBox.resumeWorkspaceReplaying(value);
         });
@@ -136,9 +136,9 @@ 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.rootDoc);
-        Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.rootDoc);
-        Doc.RemFromMyOverlay(value.rootDoc);
+        (ffView?.ComponentView as CollectionFreeFormView).props.addDocument?.(value.Document);
+        Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document);
+        Doc.RemFromMyOverlay(value.Document);
         Doc.UserDoc().currentRecording = undefined;
         Doc.UserDoc().workspaceReplayingState = undefined;
         Doc.UserDoc().workspaceRecordingState = undefined;
@@ -200,7 +200,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         getControls={this.getControls}
                         setResult={this.setResult}
                         setDuration={this.setVideoDuration}
-                        id={DocCast(this.rootDoc.proto)?.[Id] || ''}
+                        id={DocCast(this.Document.proto)?.[Id] || ''}
                     />
                 )}
             </div>
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index b2512a0e5..224bc6f67 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -290,8 +290,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationText._layout_autoHeight = false;
         const dictationTextProto = Doc.GetProto(dictationText);
         dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
-        dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`self.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
-        dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`);
+        dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+        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();
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 197c520c7..a51a83b26 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -200,7 +200,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
         if (this.onCompile()) {
             const bindings: { [name: string]: any } = {};
             this.paramsNames.forEach(key => (bindings[key] = this.rootDoc[key]));
-            // binds vars so user doesnt have to refer to everything as self.<var>
+            // binds vars so user doesnt have to refer to everything as this.<var>
             ScriptCast(this.rootDoc[this.fieldKey], null)?.script.run({ ...bindings, self: this.rootDoc, this: this.layoutDoc }, this.onError);
         }
     };
@@ -748,7 +748,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(true) && 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) => (
@@ -815,7 +815,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(true) && 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/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8bf2f4ce5..f87d94784 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1054,10 +1054,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
                     overflow: this.props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
-                }}
-                onWheel={e => {
-                    e.stopPropagation();
-                    e.preventDefault();
                 }}>
                 <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
                     <div
@@ -1097,7 +1093,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
-                            rootDoc={this.rootDoc}
+                            Document={this.rootDoc}
                             scrollTop={0}
                             annotationLayerScrollTop={0}
                             scaling={returnOne}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index e42fb4a03..5c46035be 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1111,7 +1111,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
                         <MarqueeAnnotator
                             ref={this._marqueeref}
-                            rootDoc={this.rootDoc}
+                            Document={this.rootDoc}
                             anchorMenuClick={this.anchorMenuClick}
                             scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
                             annotationLayerScrollTop={0}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b65c440d1..dd4be7fcd 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -316,7 +316,7 @@ 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() && 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();
@@ -388,7 +388,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     this._editorView.updateState(EditorState.fromJSON(this.config, json));
                 }
             }
-            if (window.getSelection()?.isCollapsed && this.props.isSelected()) {
+            if (window.getSelection()?.isCollapsed && this.props.rootSelected()) {
                 AnchorMenu.Instance.fadeOut(true);
             }
         }
@@ -576,9 +576,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         if (dragData) {
             const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
             const effectiveAcl = GetEffectiveAcl(dataDoc);
-            let added = [AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl);
             const draggedDoc = dragData.draggedDocuments.lastElement();
-            if (added) {
+            let added: Opt<boolean>;
+            if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
                 // replace text contents when dragging with Alt
                 if (de.altKey) {
                     const fieldKey = Doc.LayoutFieldKey(draggedDoc);
@@ -613,8 +613,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     }
                 }
             } // otherwise, fall through to outer collection to handle drop
-            !added && e.preventDefault();
-            e.stopPropagation();
+            added === false && e.preventDefault();
+            added === true && e.stopPropagation();
             return added;
         }
         return false;
@@ -1256,17 +1256,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         );
 
         this._disposers.selected = reaction(
-            () => this.props.isSelected(),
+            () => this.props.rootSelected(),
             action(selected => {
                 //selected && setTimeout(() => this.prepareForTyping());
                 if (FormattedTextBox._globalHighlights.has('Bold Text')) {
                     this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
                 }
                 if (RichTextMenu.Instance?.view === this._editorView && !selected) {
-                    RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
+                    RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
                 }
                 if (this._editorView && selected) {
-                    RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
+                    RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
                     setTimeout(this.autoLink, 20);
                 }
             }),
@@ -1451,7 +1451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const self = this;
         return new Plugin({
             view(newView) {
-                runInAction(() => self.props.isSelected() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
+                runInAction(() => self.props.rootSelected() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView));
                 return new RichTextMenuPlugin({ editorProps: this.props });
             },
         });
@@ -1588,7 +1588,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         FormattedTextBox.LiveTextUndo = undefined;
         this.unhighlightSearchTerms();
         this._editorView?.destroy();
-        RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
+        RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none');
     }
 
@@ -1624,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() || this.props.isSelected()) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+        if (e.button === 0 && (this.props.rootSelected() || 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
@@ -1658,7 +1658,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @action
     onDoubleClick = (e: React.MouseEvent): void => {
         FormattedTextBoxComment.textBox = this;
-        if (e.button === 0 && this.props.isSelected() && !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
@@ -1669,7 +1669,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
         FormattedTextBoxComment.Hide();
 
-        if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+        if (e.buttons === 1 && this.props.rootSelected() && !e.altKey) {
             e.stopPropagation();
         }
     };
@@ -1680,8 +1680,9 @@ 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.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props, this.layoutDoc);
         e.stopPropagation();
     };
 
@@ -1715,7 +1716,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()) {
+        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);
@@ -1729,7 +1730,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()) {
+        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;
@@ -1766,6 +1767,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @action
     onBlur = (e: any) => {
+        console.log('BLUREd = ' + this.layoutDoc.title + ' ' + this.props.rootSelected());
         if (this.ProseRef?.children[0] !== e.nativeEvent.target) return;
         if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) {
             const stordMarks = this._editorView?.state.storedMarks?.slice();
@@ -1778,8 +1780,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 tr && this._editorView.dispatch(tr);
             }
         }
-        if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected()) {
-            RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
+        if (RichTextMenu.Instance?.view === this._editorView && !this.props.rootSelected()) {
+            RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
         }
         FormattedTextBox._hadSelection = window.getSelection()?.toString() !== '';
         this.endUndoTypingBatch();
@@ -1846,7 +1848,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
                 (document.activeElement as any).blur?.();
                 SelectionManager.DeselectAll();
-                RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
+                RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
                 return;
             case 'Enter':
                 this.insertTime();
@@ -2073,10 +2075,13 @@ 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' >
+            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(this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1)) && //
-                (this.props.isSelected() || this.isAnyChildContentActive())
+                (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil((height ? height : this.props.PanelHeight()) / scale) && //
+                (this.props.rootSelected() || this.isAnyChildContentActive())
             ) {
                 e.preventDefault();
             }
@@ -2103,7 +2108,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         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.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props.  e.g., <FormattedTextBox height='{this._headerHeight}px' >
+        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"
@@ -2135,7 +2140,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     ref={this._ref}
                     style={{
                         cursor: this.props.isContentActive() ? 'text' : undefined,
-                        height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined),
+                        height: this.props.height ? 'max-content' : undefined,
                         pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none',
                     }}
                     onContextMenu={this.specificContextMenu}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 712be91c8..7f9bfe753 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -9,19 +9,19 @@ import { wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { Doc } from '../../../../fields/Doc';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
+import { numberRange } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { EquationBox } from '../EquationBox';
 import { FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
 import { updateBullets } from './ProsemirrorExampleTransfer';
 import './RichTextMenu.scss';
 import { schema } from './schema_rts';
-import { EquationBox } from '../EquationBox';
-import { numberRange } from '../../../../Utils';
 const { toggleMark } = require('prosemirror-commands');
 
 @observer
@@ -30,6 +30,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
     private _linkToRef = React.createRef<HTMLInputElement>();
 
+    layoutDoc: Doc | undefined;
     @observable public view?: EditorView;
     public editorProps: FieldViewProps | undefined;
 
@@ -66,7 +67,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         super(props);
         runInAction(() => {
             RichTextMenu.Instance = this;
-            this.updateMenu(undefined, undefined, props);
+            this.updateMenu(undefined, undefined, props, this.layoutDoc);
             this._canFade = false;
             this.Pinned = true;
         });
@@ -102,11 +103,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @computed get textAlign() {
         return this._activeAlignment;
     }
+    @computed get textVcenter() {
+        return BoolCast(this.layoutDoc?.layout_centered);
+    }
     _disposer: IReactionDisposer | undefined;
     componentDidMount() {
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
-            views => this.updateMenu(undefined, undefined, undefined)
+            views => this.updateMenu(undefined, undefined, undefined, undefined)
         );
     }
     componentWillUnmount() {
@@ -114,11 +118,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     @action
-    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) {
+    public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any, layoutDoc: Doc | undefined) {
         if (this._linkToRef.current?.getBoundingClientRect().width) {
             return;
         }
         this.view = view;
+        this.layoutDoc = layoutDoc;
         props && (this.editorProps = props);
 
         // Don't do anything if the document/selection didn't change
@@ -181,7 +186,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveAlignment() {
-        if (this.view && this.TextView?.props.isSelected()) {
+        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) {
@@ -194,7 +199,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     // finds font sizes and families in selection
     getActiveListStyle() {
-        if (this.view && this.TextView?.props.isSelected()) {
+        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) {
@@ -214,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.isSelected()) {
+        if (this.view && this.TextView?.props.rootSelected()) {
             const state = this.view.state;
             const pos = this.view.state.selection.$from;
             const marks: Mark[] = [...(state.storedMarks ?? [])];
@@ -249,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.isSelected()) 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);
@@ -285,10 +290,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return activeMarks;
     }
 
-    destroy() {
-        !this.TextView?.props.isSelected() && this.fadeOut(true);
-    }
-
     @action
     setActiveMarkButtons(activeMarks: MarkType[] | undefined) {
         if (!activeMarks) return;
@@ -360,7 +361,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) {
             SelectionManager.Views().forEach(dv => (dv.rootDoc._text_fontSize = fontSize));
         } else Doc.UserDoc().fontSize = fontSize;
-        this.updateMenu(this.view, undefined, this.props);
+        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     };
 
     setFontFamily = (family: string) => {
@@ -369,7 +370,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.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     };
 
     setHighlight(color: string) {
@@ -378,7 +379,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.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     }
 
     setColor(color: string) {
@@ -387,7 +388,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.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     }
 
     // TODO: remove doesn't work
@@ -428,7 +429,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
             }
         }
         this.view.focus();
-        this.updateMenu(this.view, undefined, this.props);
+        this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
     };
 
     insertSummarizer(state: EditorState, dispatch: any) {
@@ -442,8 +443,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
         return true;
     }
 
+    vcenterToggle = (view: EditorView, dispatch: any) => {
+        this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered);
+    };
     align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => {
-        if (this.TextView?.props.isSelected()) {
+        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)) {
@@ -875,6 +879,6 @@ export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps>
         return null;
     }
     update(view: EditorView, lastState: EditorState | undefined) {
-        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps);
+        RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc);
     }
 }
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 3e2afd2ce..701ee4053 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -95,7 +95,7 @@ export class RichTextRules {
                 textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
                 textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
                 textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]]
-                textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`);
+                textDocInline._textContext = ComputedField.MakeFunction(`copyField(this.${inlineFieldKey})`);
                 textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
                 textDoc[inlineFieldKey] = ''; // set a default value for the annotation
                 const node = (state.doc.resolve(start) as any).nodeAfter;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 789509784..bb0ed0943 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -75,7 +75,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     constructor(props: any) {
         super(props);
         if (!PresBox.navigateToDocScript) {
-            PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentation_targetDoc, self)')!;
+            PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
         }
     }
 
@@ -114,16 +114,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return StrCast(this.layoutDoc.presFieldKey, 'data');
     }
     @computed get childDocs() {
-        return DocListCast(this.rootDoc[this.presFieldKey]);
+        return DocListCast(this.Document[this.presFieldKey]);
     }
     @computed get tagDocs() {
         return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
     }
     @computed get itemIndex() {
-        return NumCast(this.rootDoc._itemIndex);
+        return NumCast(this.Document._itemIndex);
     }
     @computed get activeItem() {
-        return DocCast(this.childDocs[NumCast(this.rootDoc._itemIndex)]);
+        return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
     }
     @computed get targetDoc() {
         return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
@@ -142,13 +142,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     @computed get selectedDocumentView() {
         if (SelectionManager.Views().length) return SelectionManager.Views()[0];
-        if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc);
+        if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
     }
     @computed get isPres() {
-        return this.selectedDoc === this.rootDoc;
+        return this.selectedDoc === this.Document;
     }
     @computed get selectedDoc() {
-        return this.selectedDocumentView?.rootDoc;
+        return this.selectedDocumentView?.Document;
     }
     isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc;
     clearSelectedArray = () => this.selectedArray.clear();
@@ -170,7 +170,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             () => this.selectedDoc,
             selected => {
                 document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
-                (this._presKeyEvents = selected === this.rootDoc) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
+                (this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
             },
             { fireImmediately: true }
         );
@@ -188,17 +188,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
         this.props.setContentView?.(this);
         this._unmounting = false;
-        if (this.props.renderDepth > 0) {
-            runInAction(() => {
-                this.rootDoc._forceRenderEngine = computeTimelineLayout.name;
-                this.layoutDoc._gridGap = 0;
-                this.layoutDoc._yMargin = 0;
-            });
-        }
         this.turnOffEdit(true);
         this._disposers.selection = reaction(
             () => SelectionManager.Views().slice(),
-            views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(),
+            views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(),
             { fireImmediately: true }
         );
         this._disposers.editing = reaction(
@@ -213,7 +206,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     updateCurrentPresentation = (pres?: Doc) => {
-        Doc.ActivePresentation = pres ?? this.rootDoc;
+        Doc.ActivePresentation = pres ?? this.Document;
         PresBox.Instance = this;
     };
 
@@ -321,7 +314,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (progressiveReveal(false)) return true;
         if (this.childDocs[this.itemIndex + 1] !== undefined) {
             // Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
-            const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]);
+            const slides = DocListCast(this.Document[StrCast(this.presFieldKey, 'data')]);
             const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
 
             // before moving onto next slide, run the subroutines :)
@@ -356,7 +349,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             // Case 2: There are no other frames so it should go to the previous slide
             prevSelected = Math.max(0, prevSelected - 1);
             this.nextSlide(prevSelected);
-            this.rootDoc._itemIndex = prevSelected;
+            this.Document._itemIndex = prevSelected;
         } else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) {
             // Case 3: Pres loop is on so it should go to the last slide
             this.nextSlide(this.childDocs.length - 1);
@@ -370,7 +363,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
-            this.rootDoc._itemIndex = index;
+            this.Document._itemIndex = index;
             if (from?.mediaStopTriggerList && this.layoutDoc.presentation_status !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -410,7 +403,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     playAnnotation = (anno: AudioField) => {};
     @action
     static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
-        const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
+        const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
         if (!bestTarget) return;
         let changed = false;
         if (pinDocLayout) {
@@ -441,7 +434,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 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);
+                    ffview.layoutDoc._currentFrame = NumCast(activeFrame);
                 }
             }
         }
@@ -600,7 +593,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 });
             setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
         }
-        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget._isGroup) {
+        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
             const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number'));
             if (contentBounds) {
                 const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -755,7 +748,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const eleViewCache = Array.from(this._eleArray);
         const resetSelection = action(() => {
             if (!this.props.isSelected()) {
-                const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
+                const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
                 if (presDocView) SelectionManager.SelectView(presDocView, false);
                 this.clearSelectedArray();
                 selViewCache.forEach(doc => this.addToSelectedArray(doc));
@@ -897,7 +890,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     }
                 });
             LightboxView.Instance.SetLightboxDoc(undefined);
-            Doc.RemFromMyOverlay(this.rootDoc);
+            Doc.RemFromMyOverlay(this.Document);
             return PresStatus.Edit;
         };
     };
@@ -936,7 +929,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // The function allows for expanding the view of pres on toggle
     @action toggleExpandMode = () => {
         runInAction(() => (this._expandBoolean = !this._expandBoolean));
-        this.rootDoc.expandBoolean = this._expandBoolean;
+        this.Document.expandBoolean = this._expandBoolean;
         this.childDocs.forEach(doc => {
             doc.presentation_expandInlineButton = this._expandBoolean;
         });
@@ -986,16 +979,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @action
     enterMinimize = () => {
-        this.updateCurrentPresentation(this.rootDoc);
+        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.rootDoc, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
+        return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]);
     };
     exitMinimize = () => {
         if (Doc.IsInMyOverlay(this.layoutDoc)) {
-            Doc.RemFromMyOverlay(this.rootDoc);
-            CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right);
+            Doc.RemFromMyOverlay(this.Document);
+            CollectionDockingView.AddSplit(this.Document, OpenWhereMod.right);
         }
         return PresStatus.Edit;
     };
@@ -1021,8 +1014,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
         this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : '');
         // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
-        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.rootDoc._pivotField = undefined);
-        this.rootDoc._type_collection = type_collection;
+        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
+        this.Document._type_collection = type_collection;
         if (this.isTreeOrStack) {
             this.layoutDoc._gridGap = 0;
         }
@@ -1083,7 +1076,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return true;
     };
     childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
-    removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
+    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;
     /**
@@ -1123,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @action
     selectPres = () => {
-        const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc);
+        const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
         presDocView && SelectionManager.SelectView(presDocView, false);
     };
 
@@ -1139,7 +1132,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (noNav) {
             const index = this.childDocs.indexOf(doc);
             if (index >= 0 && index < this.childDocs.length) {
-                this.rootDoc._itemIndex = index;
+                this.Document._itemIndex = index;
             }
         } else {
             this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
@@ -1237,8 +1230,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case 'ArrowRight':
                 if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) {
                     // TODO: update to work properly
-                    this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1;
-                    this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
+                    this.Document._itemIndex = NumCast(this.Document._itemIndex) + 1;
+                    this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
                 } else {
                     this.next();
                     if (this._presTimer) {
@@ -1254,8 +1247,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             case 'ArrowLeft':
                 if (e.shiftKey && this.itemIndex !== 0) {
                     // TODO: update to work properly
-                    this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1;
-                    this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]);
+                    this.Document._itemIndex = NumCast(this.Document._itemIndex) - 1;
+                    this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
                 } else {
                     this.back();
                     if (this._presTimer) {
@@ -1640,8 +1633,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(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
+                                else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
                             }}
                             checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
                         />
@@ -2221,7 +2214,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
             const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab;
             const data = Cast(presCollection?.data, listSpec(Doc));
-            const config_data = Cast(this.rootDoc.data, listSpec(Doc));
+            const config_data = Cast(this.Document.data, listSpec(Doc));
             if (data && config_data) {
                 data.push(doc);
                 this.props.pinToPres(doc, {});
@@ -2297,11 +2290,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @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 mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
         const activeColor = SettingsManager.userVariantColor;
         const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
-        return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.rootDoc) ? null : (
+        return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
             <div id="toolbarContainer" className={'presBox-toolbar'}>
                 {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
                     <FontAwesomeIcon icon={"plus"} />
@@ -2340,12 +2333,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * presentPanel: The button to start the presentation / open minimized view of the presentation
      */
     @computed get topPanel() {
-        const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
         return (
             <div
-                className={`presBox-buttons${Doc.IsInMyOverlay(this.rootDoc) ? ' inOverlay' : ''}`}
-                style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}>
+                className={`presBox-buttons${Doc.IsInMyOverlay(this.Document) ? ' inOverlay' : ''}`}
+                style={{ background: Doc.ActivePresentation === this.Document ? Colors.LIGHT_BLUE : undefined, display: !this.Document._chromeHidden ? 'none' : undefined }}>
                 {isMini ? null : (
                     <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presentation_status === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
                         <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
@@ -2398,7 +2391,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: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
-        const inOverlay = Doc.IsInMyOverlay(this.rootDoc);
+        const inOverlay = Doc.IsInMyOverlay(this.Document);
         // Case 1: There are still other frames and should go through all frames before going to next slide
         return (
             <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
@@ -2569,7 +2562,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     render() {
         // needed to ensure that the childDocs are loaded for looking up fields
         this.childDocs.slice();
-        const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType;
+        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
         const presEnd =
             !this.layoutDoc.presLoop &&
             this.itemIndex === this.childDocs.length - 1 &&
@@ -2579,7 +2572,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
                 <div
                     className="presPanelOverlay"
-                    style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.rootDoc ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
+                    style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
                     <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                         <div
                             className="presPanel-button"
@@ -2617,7 +2610,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 </div>
             </div>
         ) : (
-            <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.rootDoc) ? PresBox.minimizedWidth : undefined }}>
+            <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.Document) ? PresBox.minimizedWidth : undefined }}>
                 {this.topPanel}
                 {this.toolbar}
                 {this.newDocumentToolbarDropdown}
@@ -2637,7 +2630,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
                                 childLayoutTemplate={this.childLayoutTemplate}
-                                childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
+                                childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
                                 filterAddDocument={this.addDocumentFilter}
                                 removeDocument={returnFalse}
                                 dontRegisterView={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 10a9395a0..53fb991e7 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -40,7 +40,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     // Idea: this boolean will determine whether to automatically show the video when this preselement is selected.
     // @observable static showVideo: boolean = false;
     @computed get indexInPres() {
-        return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
+        return DocListCast(this.presBox?.[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.Document);
     } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
     @computed get expandViewHeight() {
         return 100;
@@ -55,16 +55,16 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.props.DocumentView?.()?.props.docViewPath().lastElement()?.ComponentView as PresBox;
     }
     @computed get presBox() {
-        return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
+        return this.props.DocumentView?.().props.docViewPath().lastElement()?.Document;
     }
     @computed get targetDoc() {
-        return Cast(this.rootDoc.presentation_targetDoc, Doc, null) || this.rootDoc;
+        return Cast(this.Document.presentation_targetDoc, Doc, null) || this.Document;
     }
 
     componentDidMount() {
         this.layoutDoc.layout_hideLinkButton = true;
         this._heightDisposer = reaction(
-            () => ({ expand: this.rootDoc.presentation_expandInlineButton, height: this.collapsedHeight }),
+            () => ({ expand: this.Document.presentation_expandInlineButton, height: this.collapsedHeight }),
             ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
             { fireImmediately: true }
         );
@@ -79,7 +79,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
 
     @action
-    presExpandDocumentClick = () => (this.rootDoc.presentation_expandInlineButton = !this.rootDoc.presentation_expandInlineButton);
+    presExpandDocumentClick = () => (this.Document.presentation_expandInlineButton = !this.Document.presentation_expandInlineButton);
 
     embedHeight = (): number => this.collapsedHeight + this.expandViewHeight;
     // embedWidth = () => this.props.PanelWidth();
@@ -94,10 +94,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * presentation element.
      */
     @computed get renderEmbeddedInline() {
-        return !this.rootDoc.presentation_expandInlineButton || !this.targetDoc ? null : (
+        return !this.Document.presentation_expandInlineButton || !this.targetDoc ? null : (
             <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
-                    Document={PresBox.targetRenderedDoc(this.rootDoc)}
+                    Document={PresBox.targetRenderedDoc(this.Document)}
                     DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
@@ -158,9 +158,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @computed get transition() {
         let transitionInS: number;
-        if (this.rootDoc.presentation_transition) transitionInS = NumCast(this.rootDoc.presentation_transition) / 1000;
+        if (this.Document.presentation_transition) transitionInS = NumCast(this.Document.presentation_transition) / 1000;
         else transitionInS = 0.5;
-        return this.rootDoc.presentation_movement === PresMovement.Jump || this.rootDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
+        return this.Document.presentation_movement === PresMovement.Jump || this.Document.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's';
     }
 
     private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -173,11 +173,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         e.stopPropagation();
         e.preventDefault();
         if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) {
-            this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true, false);
+            this.presBoxView?.regularSelect(this.Document, this._itemRef.current!, this._dragRef.current!, true, false);
             setupMoveUpEvents(this, e, this.startDrag, emptyFunction, e => {
                 e.stopPropagation();
                 e.preventDefault();
-                this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+                this.presBoxView?.modifierSelect(this.Document, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
                 this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem);
             });
         }
@@ -188,10 +188,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     startDrag = (e: PointerEvent) => {
         const miniView: boolean = this.toolbarWidth <= 100;
-        const activeItem = this.rootDoc;
+        const activeItem = this.Document;
         const dragArray = this.presBoxView?._dragArray ?? [];
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
-        if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+        if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.Document);
         dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
         dragData.moveDocument = this.props.moveDocument;
         const dragItem: HTMLElement[] = [];
@@ -273,8 +273,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) {
             runInAction(() => (this.presBox!.itemIndex = (this.presBoxView?.itemIndex || 0) - 1));
         }
-        this.props.removeDocument?.(this.rootDoc);
-        this.presBoxView?.removeFromSelectedArray(this.rootDoc);
+        this.props.removeDocument?.(this.Document);
+        this.presBoxView?.removeFromSelectedArray(this.Document);
         this.removeAllRecordingInOverlay();
     }, 'Remove doc from pres trail');
 
@@ -282,7 +282,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @undoBatch
     @action
     onSetValue = (value: string) => {
-        this.rootDoc.title = !value.trim().length ? '-untitled-' : value;
+        this.Document.title = !value.trim().length ? '-untitled-' : value;
         return true;
     };
 
@@ -328,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     @computed get recordingIsInOverlay() {
-        return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc);
+        return Doc.MyOverlayDocs.some(doc => doc.slides === this.Document);
     }
 
     // a previously recorded video will have timecode defined
@@ -338,7 +338,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     removeAllRecordingInOverlay = () => {
-        Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay);
+        Doc.MyOverlayDocs.filter(doc => doc.slides === this.Document).forEach(Doc.RemFromMyOverlay);
     };
 
     static removeEveryExistingRecordingInOverlay = () => {
@@ -413,10 +413,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     lfg = (e: React.MouseEvent) => {
         e.stopPropagation();
         // TODO: fix this bug
-        const { toggleChildrenRun } = this.rootDoc;
-        TreeView.ToggleChildrenRun.get(this.rootDoc)?.();
+        const { toggleChildrenRun } = this.Document;
+        TreeView.ToggleChildrenRun.get(this.Document)?.();
 
-        // call this.rootDoc.recurChildren() to get all the children
+        // call this.Document.recurChildren() to get all the children
         // if (iconClick) PresElementBox.showVideo = false;
     };
 
@@ -434,8 +434,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const presBoxColor = StrCast(presBox?._backgroundColor);
         const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
         const targetDoc = this.targetDoc;
-        const activeItem = this.rootDoc;
-        const hasChildren = BoolCast(this.rootDoc?.hasChildren);
+        const activeItem = this.Document;
+        const hasChildren = BoolCast(this.Document?.hasChildren);
 
         const items: JSX.Element[] = [];
 
@@ -498,14 +498,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             );
         }
         items.push(
-            <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+            <Tooltip key="eye" title={<div className="dash-tooltip">{this.Document.presentation_expandInlineButton ? 'Minimize' : 'Expand'}</div>}>
                 <div
                     className="slideButton"
                     onClick={e => {
                         e.stopPropagation();
                         this.presExpandDocumentClick();
                     }}>
-                    <FontAwesomeIcon icon={this.rootDoc.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+                    <FontAwesomeIcon icon={this.Document.presentation_expandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
                 </div>
             </Tooltip>
         );
@@ -536,18 +536,18 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get mainItem() {
-        const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
+        const isSelected: boolean = this.selectedArray?.has(this.Document) ? true : false;
         const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres;
         const miniView: boolean = this.toolbarWidth <= 110;
         const presBox = this.presBox; //presBox
         const presBoxColor: string = StrCast(presBox?._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const activeItem: Doc = this.rootDoc;
+        const activeItem: Doc = this.Document;
 
         return (
             <div
                 className="presItem-container"
-                key={this.props.Document[Id] + this.indexInPres}
+                key={this.Document[Id] + this.indexInPres}
                 ref={this._itemRef}
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
@@ -559,7 +559,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 }}
                 onDoubleClick={action(e => {
                     this.toggleProperties();
-                    this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
+                    this.presBoxView?.regularSelect(this.Document, this._itemRef.current!, this._dragRef.current!, false);
                 })}
                 onPointerOver={this.onPointerOver}
                 onPointerLeave={this.onPointerLeave}
@@ -571,7 +571,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 ) : (
                     <div
                         ref={this._dragRef}
-                        className={`presItem-slide ${isCurrent ? 'active' : ''}${this.rootDoc.runProcess ? ' testingv2' : ''}`}
+                        className={`presItem-slide ${isCurrent ? 'active' : ''}${this.Document.runProcess ? ' testingv2' : ''}`}
                         style={{
                             display: 'infline-block',
                             backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
@@ -583,7 +583,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             style={{
                                 display: 'inline-flex',
                                 pointerEvents: isSelected ? undefined : 'none',
-                                width: `calc(100% ${this.rootDoc.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
+                                width: `calc(100% ${this.Document.presentation_expandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
                                 cursor: isSelected ? 'text' : 'grab',
                             }}>
                             <div
@@ -611,6 +611,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     render() {
-        return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
+        return !(this.Document instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem;
     }
 }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index ca9bf7bd2..33a336de3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -34,7 +34,6 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/
 
 interface IViewerProps extends FieldViewProps {
     Document: Doc;
-    rootDoc: Doc;
     dataDoc: Doc;
     layoutDoc: Doc;
     fieldKey: string;
@@ -168,7 +167,7 @@ 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.rootDoc && mainCont) {
+        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) {
@@ -577,7 +576,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     {!this._mainCont.current || !this._annotationLayer.current ? null : (
                         <MarqueeAnnotator
                             ref={this._marqueeref}
-                            rootDoc={this.props.rootDoc}
+                            Document={this.props.Document}
                             getPageFromScroll={this.getPageFromScroll}
                             anchorMenuClick={this.props.anchorMenuClick}
                             scrollTop={0}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index aa46e57dc..74464181f 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -186,7 +186,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     @action
     searchCollection(query: string) {
         this._selectedResult = undefined;
-        this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.rootDoc, query);
+        this._results = SearchUtil.SearchCollection(CollectionDockingView.Instance?.Document, query);
 
         this.computePageRanks();
     }
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e2746091e..993715e6f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -537,7 +537,7 @@ export namespace Doc {
         embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`);
         embedding.author = Doc.CurrentUserEmail;
 
-        Doc.AddDocToList(Doc.GetProto(doc)[DocData], 'proto_embeddings', embedding);
+        Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding);
 
         return embedding;
     }
@@ -788,8 +788,9 @@ export namespace Doc {
                             const dataDoc = Doc.GetProto(targetDoc);
                             newLayoutDoc.resolvedDataDoc = dataDoc;
                             newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
-                            if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) {
-                                dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
+                            if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as any)?.length) {
+                                dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
+                                // ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"])`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
                             }
                             targetDoc[expandedLayoutFieldKey] = newLayoutDoc;
 
@@ -867,7 +868,7 @@ export namespace Doc {
     }
 
     export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
-        const copy = new Doc(copyProtoId, true);
+        const copy = runInAction(() => new Doc(copyProtoId, true));
         updateCachedAcls(copy);
         const exclude = [...StrListCast(doc.cloneFieldFilter), 'dragFactory_count', 'cloneFieldFilter'];
         Object.keys(doc)
@@ -900,7 +901,7 @@ export namespace Doc {
             Doc.GetProto(copy).embedContainer = undefined;
             Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]);
         } else {
-            Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy);
+            Doc.AddDocToList(copy[DocData], 'proto_embeddings', copy);
         }
         copy.embedContainer = undefined;
         if (retitle) {
@@ -1305,12 +1306,12 @@ export namespace Doc {
         });
     }
 
-    export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) {
+    export function styleFromLayoutString(doc: Doc, props: any, scale: number) {
         const style: { [key: string]: any } = {};
         const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
         const replacer = (match: any, expr: string, offset: any, string: any) => {
             // bcz: this executes a script to convert a property expression string:  { script }  into a value
-            return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: rootDoc, this: layoutDoc, scale }).result?.toString() ?? '';
+            return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
         };
         divKeys.map((prop: string) => {
             const p = props[prop];
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 2b8750714..cd07a8885 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -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: Cast(doc.rootDocument, Doc, null) ?? doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
+    _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
 
     [ToValue](doc: Doc) {
         return ComputedField.toValue(doc, this);
@@ -167,35 +167,35 @@ export class ComputedField extends ScriptField {
         return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined;
     }
     public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) {
-        if (!doc[`${fieldKey}-indexed`]) {
+        if (!doc[`${fieldKey}_indexed`]) {
             const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]);
             flist[curTimecode] = Cast(doc[fieldKey], 'number', null);
-            doc[`${fieldKey}-indexed`] = flist;
+            doc[`${fieldKey}_indexed`] = flist;
         }
-        const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, ${defaultVal})`, {}, true, {});
-        const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
+        const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, ${defaultVal})`, {}, true, {});
+        const setField = ScriptField.CompileScript(`setIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, value)`, { value: 'any' }, true, {});
         return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
     }
     public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
-        if (!doc[`${fieldKey}-indexed`]) {
+        if (!doc[`${fieldKey}_`]) {
             const flist = new List<string>(numberRange(curTimecode + 1).map(i => undefined) as any as string[]);
             flist[curTimecode] = StrCast(doc[fieldKey]);
-            doc[`${fieldKey}-indexed`] = flist;
+            doc[`${fieldKey}_indexed`] = flist;
         }
-        const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
-        const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
+        const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey})`, {}, true, {});
+        const setField = ScriptField.CompileScript(`setIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, value)`, { value: 'any' }, true, {});
         return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
     }
     public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) {
         if (doc[`${fieldKey}`] instanceof List) return;
-        if (!doc[`${fieldKey}-indexed`]) {
+        if (!doc[`${fieldKey}_indexed`]) {
             const flist = new List<Field>(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]);
             flist[curTimecode] = Field.Copy(doc[fieldKey]);
-            doc[`${fieldKey}-indexed`] = flist;
+            doc[`${fieldKey}_indexed`] = flist;
         }
-        const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
+        const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey})`, {}, true, {});
         const setField = ScriptField.CompileScript(
-            `{setIndexVal (self['${fieldKey}-indexed'], self.${interpolatorKey}, value); console.log(self["${fieldKey}-indexed"][self.${interpolatorKey}],self.data,self["${fieldKey}-indexed"]))}`,
+            `{setIndexVal (this['${fieldKey}_indexed'], this.${interpolatorKey}, value); console.log(this["${fieldKey}_indexed"][this.${interpolatorKey}],this.data,this["${fieldKey}_indexed"]))}`,
             { value: 'any' },
             false,
             {}
-- 
cgit v1.2.3-70-g09d2


From 9ae07e29f41df9d64985c25b67054843a99a0224 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 1 Dec 2023 02:33:04 -0500
Subject: converted props.DataDoc to props.TemplateDataDocument and fixed so
 that it's always undefined unless it's a template.  converted references from
 rootDocument to props.TemplateDataDocument.

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/DocComponent.tsx                  | 13 +++----
 src/client/views/GestureOverlay.tsx                |  1 -
 src/client/views/LightboxView.tsx                  |  1 -
 src/client/views/MainView.tsx                      |  6 ----
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/PropertiesButtons.tsx             |  2 +-
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 src/client/views/StyleProvider.tsx                 | 21 ++++++-----
 .../views/collections/CollectionCarousel3DView.tsx |  4 +--
 .../views/collections/CollectionCarouselView.tsx   |  4 +--
 .../collections/CollectionMasonryViewFieldRow.tsx  |  2 +-
 src/client/views/collections/CollectionMenu.tsx    |  1 -
 .../views/collections/CollectionNoteTakingView.tsx | 10 +++---
 .../collections/CollectionNoteTakingViewColumn.tsx |  8 ++---
 .../collections/CollectionStackedTimeline.tsx      |  2 +-
 .../views/collections/CollectionStackingView.tsx   | 10 +++---
 .../CollectionStackingViewFieldColumn.tsx          |  8 ++---
 src/client/views/collections/CollectionSubView.tsx | 24 +++++++------
 .../views/collections/CollectionTreeView.tsx       |  8 ++---
 src/client/views/collections/TabDocView.tsx        |  2 +-
 src/client/views/collections/TreeView.tsx          |  4 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 12 +++----
 .../collectionGrid/CollectionGridView.tsx          |  4 +--
 .../CollectionMulticolumnView.tsx                  |  2 +-
 .../CollectionMultirowView.tsx                     |  2 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  2 --
 .../collectionSchema/SchemaTableCell.tsx           |  2 +-
 src/client/views/linking/LinkPopup.tsx             |  1 -
 src/client/views/newlightbox/NewLightboxView.tsx   |  1 -
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  4 +--
 src/client/views/nodes/ComparisonBox.tsx           |  2 +-
 src/client/views/nodes/DocumentContentsView.tsx    | 41 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            | 20 +++++------
 src/client/views/nodes/FieldView.tsx               |  1 -
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 30 ++++++++--------
 src/client/views/nodes/KeyValuePair.tsx            |  1 -
 src/client/views/nodes/LoadingBox.tsx              |  2 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |  4 +--
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 14 ++++----
 src/client/views/nodes/ScreenshotBox.tsx           |  8 ++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 13 ++++---
 .../formattedText/ProsemirrorExampleTransfer.ts    |  4 +--
 .../views/nodes/importBox/ImportElementBox.tsx     |  1 -
 src/client/views/nodes/trails/PresElementBox.tsx   |  3 +-
 src/client/views/topbar/TopBar.tsx                 |  1 -
 src/fields/Doc.ts                                  | 12 +++----
 src/mobile/MobileInterface.tsx                     |  1 -
 49 files changed, 158 insertions(+), 169 deletions(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dfb9f4327..929660ff4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1678,7 +1678,7 @@ export namespace DocUtils {
                         newDoc.x = x;
                         newDoc.y = y;
                         EquationBox.SelectOnLoad = newDoc[Id];
-                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id];
+                        if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
                         if (pivotField) {
                             newDoc[pivotField] = pivotValue;
                         }
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 04956d974..d104eb90c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -16,7 +16,6 @@ import { CollectionFreeFormView } from './collections/collectionFreeForm';
 ///  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;
-    fieldKey?: string;
     LayoutTemplate?: () => Opt<Doc>;
     LayoutTemplateString?: string;
 }
@@ -34,10 +33,6 @@ export function DocComponent<P extends DocComponentProps>() {
         @computed get dataDoc() {
             return this.props.Document[DocData] as Doc;
         }
-        // key where data is stored
-        @computed get fieldKey() {
-            return this.props.fieldKey;
-        }
     }
     return Component;
 }
@@ -45,7 +40,7 @@ export function DocComponent<P extends DocComponentProps>() {
 /// FieldViewBoxProps  -  a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox)
 interface ViewBoxBaseProps {
     Document: Doc;
-    DataDoc?: Doc;
+    TemplateDataDocument?: Doc;
     DocumentView?: () => DocumentView;
     fieldKey: string;
     isSelected: () => boolean;
@@ -65,7 +60,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
         }
         // 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.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
+            return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
         }
         // key where data is stored
         @computed get fieldKey() {
@@ -78,7 +73,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
 ///  DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image)
 export interface ViewBoxAnnotatableProps {
     Document: Doc;
-    DataDoc?: Doc;
+    TemplateDataDocument?: Doc;
     fieldKey: string;
     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)
     isContentActive: () => boolean | undefined;
@@ -102,7 +97,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
         }
         // 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.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DocData];
+            return this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc ? this.props.TemplateDataDocument ?? this.props.Document[DocData] : this.props.Document[DocData];
         }
 
         // key where data is stored
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 508339efc..559f245d9 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -466,7 +466,6 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
         this._clipboardDoc = (
             <DocumentView
                 Document={doc}
-                DataDoc={undefined}
                 addDocument={undefined}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 15266071f..de2ce3189 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -247,7 +247,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                         <DocumentView
                             ref={action((r: DocumentView | null) => (this._docView = r !== null ? r : undefined))}
                             Document={this._doc}
-                            DataDoc={undefined}
                             PanelWidth={this.lightboxWidth}
                             PanelHeight={this.lightboxHeight}
                             LayoutTemplate={this.lightboxDocTemplate}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3b3b43469..2e3817cb6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -599,7 +599,6 @@ export class MainView extends React.Component {
                 <DocumentView
                     key="headerBarDoc"
                     Document={this.headerBarDoc}
-                    DataDoc={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
                     docViewPath={returnEmptyDoclist}
@@ -634,7 +633,6 @@ export class MainView extends React.Component {
                 <DocumentView
                     key="main"
                     Document={this.mainContainer!}
-                    DataDoc={undefined}
                     addDocument={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
@@ -729,7 +727,6 @@ export class MainView extends React.Component {
                 <div className="mainView-contentArea">
                     <DocumentView
                         Document={this._sidebarContent.proto || this._sidebarContent}
-                        DataDoc={undefined}
                         addDocument={undefined}
                         addDocTab={DocumentViewInternal.addDocTabFunc}
                         pinToPres={TabDocView.PinDoc}
@@ -759,7 +756,6 @@ export class MainView extends React.Component {
             <div key="menu" className="mainView-leftMenuPanel" style={{ background: SettingsManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}>
                 <DocumentView
                     Document={Doc.MyLeftSidebarMenu}
-                    DataDoc={undefined}
                     addDocument={undefined}
                     addDocTab={DocumentViewInternal.addDocTabFunc}
                     pinToPres={emptyFunction}
@@ -897,7 +893,6 @@ export class MainView extends React.Component {
             <div className="mainView-docButtons" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }} ref={this._docBtnRef}>
                 <CollectionLinearView
                     Document={Doc.MyDockedBtns}
-                    DataDoc={undefined}
                     fieldKey="data"
                     dropAction="embed"
                     setHeight={returnFalse}
@@ -983,7 +978,6 @@ export class MainView extends React.Component {
                 select={returnFalse}
                 isSelected={returnFalse}
                 Document={this.headerBarDoc}
-                DataDoc={undefined}
                 addDocTab={returnFalse}
                 pinToPres={emptyFunction}
                 docViewPath={returnEmptyDoclist}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index b9b7e3f0b..70a44a08a 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -185,7 +185,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
 
             const targetCreator = (annotationOn: Doc | undefined) => {
                 const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-                FormattedTextBox.SelectOnLoad = target[Id];
+                FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
             DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 7a8a0bfb9..6635aabf9 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -91,7 +91,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     //containerDoc._freeform_fitContentsToBox =
                     containerDoc._isLightbox = !containerDoc._isLightbox;
                     //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
-                    const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
+                    const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]);
                     //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)));
                 });
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index cde7eeb08..c5b0528af 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -293,7 +293,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 <div ref={this.propertiesDocViewRef} style={{ pointerEvents: 'none', display: 'inline-block', height: panelHeight() }} key={this.selectedDoc[Id]}>
                     <DocumentView
                         Document={this.selectedDoc}
-                        DataDoc={this.dataDoc}
+                        TemplateDataDocument={Doc.AreProtosEqual(this.dataDoc, this.selectedDoc) ? undefined : this.dataDoc}
                         renderDepth={1}
                         fitContentsToBox={returnTrue}
                         styleProvider={DefaultStyleProvider}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 9377aabe9..8e62ed999 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -77,7 +77,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             _text_fontSize: StrCast(Doc.UserDoc().fontSize),
             _text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
         });
-        FormattedTextBox.SelectOnLoad = target[Id];
+        FormattedTextBox.SetSelectOnLoad(target);
         FormattedTextBox.DontSelectInitialText = true;
         const link = DocUtils.MakeLink(anchor, target, { link_relationship: 'inline comment:comment on' });
         link && (link.link_displayLine = false);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index fa2768d79..2dd58fa80 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -64,6 +64,9 @@ function toggleLockedPosition(doc: Doc) {
 export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
     return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
 }
+export function testFieldProps(toBeDetermined: any): toBeDetermined is FieldViewProps {
+    return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
+}
 
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
     return `M ${pw * 0.5} ${ph * inset}   C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset}   C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3}   C ${
@@ -77,19 +80,20 @@ 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>, property: string): any {
+export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps | 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');
     const isAnnotated = property.includes(':annotated');
-    const isInk = () => doc?._layout_isSvg && !props?.LayoutTemplateString;
+    const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString;
     const isOpen = property.includes(':open');
     const isEmpty = property.includes(':empty');
     const boxBackground = property.includes(':box');
-    const fieldKey = props?.fieldKey ? props.fieldKey + '_' : isCaption ? 'caption_' : '';
+    const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
     const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
     const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -132,20 +136,21 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return  undefined;
         case StyleProp.DocContents:return undefined;
         case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
-        case StyleProp.Opacity:    return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
+        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)
+        case StyleProp.TitleHeight:
+            return (props?.ScreenToLocalTransform().Scale ?? 1)*(props?.NativeDimScaling?.()??1) * NumCast(Doc.UserDoc().headerHeight,30)
         case StyleProp.ShowTitle:
             return (
                 (doc &&
-                    !props?.LayoutTemplateString &&
+                    !docProps?.LayoutTemplateString &&
                     !doc.presentation_targetDoc &&
-                    !props?.LayoutTemplateString?.includes(KeyValueBox.name) &&
+                    !docProps?.LayoutTemplateString?.includes(KeyValueBox.name) &&
                     props?.layout_showTitle?.() !== '' &&
                     StrCast(
                         doc._layout_showTitle,
@@ -259,7 +264,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             }
         }
         case StyleProp.PointerEvents:
-            if (StrCast(doc?.pointerEvents) && !props?.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 (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?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
             if (DocumentView.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
             if (props?.pointerEvents?.() === 'none') return 'none';
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index b3d8874d0..6715e5675 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -61,6 +61,8 @@ export class CollectionCarousel3DView extends CollectionSubView() {
             return (
                 <DocumentView
                     {...this.props}
+                    Document={childPair.layout}
+                    TemplateDataDocument={childPair.data}
                     //suppressSetHeight={true}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
@@ -70,8 +72,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     renderDepth={this.props.renderDepth + 1}
                     LayoutTemplate={this.props.childLayoutTemplate}
                     LayoutTemplateString={this.props.childLayoutString}
-                    Document={childPair.layout}
-                    DataDoc={childPair.data}
                     focus={this.focus}
                     ScreenToLocalTransform={this.childScreenToLocal}
                     isContentActive={this.isChildContentActive}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bcccf0044..c873ef891 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -71,7 +71,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         LayoutTemplate={this.props.childLayoutTemplate}
                         LayoutTemplateString={this.props.childLayoutString}
                         Document={curDoc.layout}
-                        DataDoc={curDoc.layout.resolvedDataDoc as Doc}
+                        TemplateDataDocument={curDoc.layout.resolvedDataDoc as Doc}
                         PanelHeight={this.panelHeight}
                         bringToFront={returnFalse}
                     />
@@ -87,7 +87,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                         marginLeft: this.marginX,
                         width: `calc(100% - ${this.marginX * 2}px)`,
                     }}>
-                    <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} DataDoc={undefined} />
+                    <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
                 </div>
             </>
         );
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 06522b85e..a9b5f401d 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -155,7 +155,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
         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.SelectOnLoad = newDoc[Id];
+        FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = value;
         (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this.props.heading);
         const docs = this.props.parent.childDocList;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index c3aba4754..8b3531da8 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -97,7 +97,6 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
             <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }}>
                 <CollectionLinearView
                     Document={selDoc}
-                    DataDoc={undefined}
                     fieldKey="data"
                     dropAction="embed"
                     setHeight={returnFalse}
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 01915f2f7..e92cb353f 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -221,7 +221,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     blockPointerEventsWhenDragging = () => (this.docsDraggedRowCol.length ? 'none' : undefined);
     // getDisplayDoc returns the rules for displaying a document in this view (ie. DocumentView)
     getDisplayDoc(doc: Doc, width: () => number) {
-        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.DataDoc;
+        const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField ? undefined : this.props.TemplateDataDocument;
         const height = () => this.getDocHeight(doc);
         let dref: Opt<DocumentView>;
         const noteTakingDocTransform = () => this.getDocTransform(doc, dref);
@@ -229,7 +229,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             <DocumentView
                 ref={r => (dref = r || undefined)}
                 Document={doc}
-                DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
+                TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
                 pointerEvents={this.blockPointerEventsWhenDragging}
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
@@ -297,7 +297,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     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.DataDoc;
+        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) || (!(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);
@@ -411,7 +411,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
             e.stopPropagation?.();
             const newDoc = Doc.MakeCopy(docView.Document, true);
             Doc.GetProto(newDoc).text = undefined;
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
     };
@@ -526,7 +526,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
                 Document={this.props.Document}
-                DataDoc={this.props.DataDoc}
+                TemplateDataDocument={this.props.TemplateDataDocument}
                 resizeColumns={this.resizeColumns}
                 renderChildren={this.children}
                 numGroupColumns={this.numGroupColumns}
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 52cc21903..9e5f09479 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -25,7 +25,7 @@ import './CollectionNoteTakingView.scss';
 
 interface CSVFieldColumnProps {
     Document: Doc;
-    DataDoc: Opt<Doc>;
+    TemplateDataDocument: Opt<Doc>;
     docList: Doc[];
     heading: string;
     pivotField: string;
@@ -134,7 +134,7 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true });
         const colValue = this.getValue(this.props.heading);
         newDoc[key] = colValue;
-        FormattedTextBox.SelectOnLoad = newDoc[Id];
+        FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
         return this.props.addDocument?.(newDoc) || false;
     };
@@ -157,14 +157,14 @@ export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColu
         ContextMenu.Instance.clearItems();
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
-        const dataDoc = this.props.DataDoc || this.props.Document;
+        const dataDoc = this.props.TemplateDataDocument || this.props.Document;
         const pivotValue = this.getValue(this.props.heading);
 
         DocUtils.addDocumentCreatorMenuItems(
             doc => {
                 const key = this.props.pivotField;
                 doc[key] = this.getValue(this.props.heading);
-                FormattedTextBox.SelectOnLoad = doc[Id];
+                FormattedTextBox.SetSelectOnLoad(doc);
                 return this.props.addDocument?.(doc);
             },
             this.props.addDocument,
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index d4daf2baf..57ea777e4 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -799,7 +799,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     NativeHeight={returnZero}
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
-                    DataDoc={undefined}
+                    TemplateDataDocument={undefined}
                     docViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this.props.styleProvider}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index c1333c90e..10ee01e0b 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -292,7 +292,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
             }
             Doc.GetProto(newDoc).text = undefined;
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
     };
@@ -313,7 +313,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     // 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 ? undefined : this.props.DataDoc;
+        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 panelWidth = () => (this.isStackingView ? width() : this.columnWidth);
@@ -323,7 +323,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
             <DocumentView
                 ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
                 Document={doc}
-                DataDoc={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)}
+                TemplateDataDocument={dataDoc ?? (Doc.AreProtosEqual(doc[DocData], doc) ? undefined : doc[DocData])}
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={panelWidth}
                 PanelHeight={panelHeight}
@@ -387,7 +387,7 @@ 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.DataDoc;
+        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);
@@ -558,7 +558,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 chromeHidden={this.chromeHidden}
                 colHeaderData={this.colHeaderData}
                 Document={this.props.Document}
-                DataDoc={this.props.DataDoc}
+                TemplateDataDocument={this.props.TemplateDataDocument}
                 renderChildren={this.children}
                 columnWidth={this.columnWidth}
                 numGroupColumns={this.numGroupColumns}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 3598d548a..9bd412fb8 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -26,7 +26,7 @@ import { Id } from '../../../fields/FieldSymbols';
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
     Document: Doc;
-    DataDoc: Opt<Doc>;
+    TemplateDataDocument: Opt<Doc>;
     docList: Doc[];
     heading: string;
     pivotField: string;
@@ -137,7 +137,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         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.SelectOnLoad = newDoc[Id];
+        FormattedTextBox.SetSelectOnLoad(newDoc);
         FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
         return this.props.addDocument?.(newDoc) || false;
     };
@@ -211,12 +211,12 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         ContextMenu.Instance.clearItems();
         const layoutItems: ContextMenuProps[] = [];
         const docItems: ContextMenuProps[] = [];
-        const dataDoc = this.props.DataDoc || 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.SelectOnLoad = doc[Id];
+                FormattedTextBox.SetSelectOnLoad(doc);
                 return this.props.addDocument?.(doc);
             },
             this.props.addDocument,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 43653ed75..f082ca0da 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -20,7 +20,7 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { DocComponent } from '../DocComponent';
+import { ViewBoxBaseComponent } from '../DocComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { LoadingBox } from '../nodes/LoadingBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
@@ -31,7 +31,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
 }
 
 export function CollectionSubView<X>(moreProps?: X) {
-    class CollectionSubView extends DocComponent<X & SubCollectionViewProps>() {
+    class CollectionSubView extends ViewBoxBaseComponent<X & SubCollectionViewProps>() {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
         protected _mainCont?: HTMLDivElement;
@@ -56,14 +56,18 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         @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
+            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.Document.rootDocument && 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.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
+        // 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() {
@@ -71,9 +75,9 @@ export function CollectionSubView<X>(moreProps?: X) {
         }
 
         @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] {
-            const { Document, DataDoc } = this.props;
+            const { Document, TemplateDataDocument } = this.props;
             const validPairs = this.childDocs
-                .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : 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));
@@ -104,8 +108,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 rootDocument = Cast(this.props.Document.rootDocument, Doc, null);
-                rawdocs = rootDocument && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDocument)] : [];
+                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);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 36ea14d92..1315decb2 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -63,7 +63,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         return this.props.Document;
     }
     @computed get dataDoc() {
-        return this.props.DataDoc || this.doc;
+        return this.props.TemplateDataDocument || this.doc;
     }
     @computed get treeViewtruncateTitleWidth() {
         return NumCast(this.doc.treeView_TruncateTitleWidth, this.panelWidth());
@@ -166,7 +166,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             const prev = ind && DocListCast(targetDataDoc[this.props.fieldKey])[ind - 1];
             this.props.removeDocument?.(doc);
             if (ind > 0 && prev) {
-                FormattedTextBox.SelectOnLoad = prev[Id];
+                FormattedTextBox.SetSelectOnLoad(prev);
                 DocumentManager.Instance.getDocumentView(prev, this.props.DocumentView?.())?.select(false);
             }
             return true;
@@ -271,7 +271,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this,
             this,
             this.doc,
-            this.props.DataDoc,
+            this.props.TemplateDataDocument,
             undefined,
             undefined,
             addDoc,
@@ -326,7 +326,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}>
                 <DocumentView
                     Document={menuDoc}
-                    DataDoc={menuDoc}
+                    TemplateDataDocument={menuDoc}
                     isContentActive={this.props.isContentActive}
                     isDocumentActive={returnTrue}
                     addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index a5cf89217..def4beca3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -444,7 +444,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString() : undefined}
                     hideTitle={this.props.keyValue}
                     Document={this._document}
-                    DataDoc={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
+                    TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
                     onBrowseClick={DocumentView.exploreMode}
                     waitForDoubleClickToClick={this.waitForDoubleClick}
                     isContentActive={this.isContentActive}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 6cd111ac4..a756007f8 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -964,7 +964,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 })}
                 Document={this.doc}
-                DataDoc={undefined} // or this.dataDoc?
                 layout_fitWidth={returnTrue}
                 scriptContext={this}
                 hideDecorationTitle={this.props.treeView.outlineMode}
@@ -1059,7 +1058,6 @@ export class TreeView extends React.Component<TreeViewProps> {
                     key={this.doc[Id]}
                     ref={action((r: DocumentView | null) => (this._dref = r))}
                     Document={this.doc}
-                    DataDoc={undefined}
                     layout_fitWidth={this.fitWidthFilter}
                     PanelWidth={this.embeddedPanelWidth}
                     PanelHeight={this.embeddedPanelHeight}
@@ -1249,7 +1247,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     const fieldKey = Doc.LayoutFieldKey(newParent);
                     if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
                         remove(child);
-                        FormattedTextBox.SelectOnLoad = child[Id];
+                        FormattedTextBox.SetSelectOnLoad(child);
                         TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined;
                         Doc.AddDocToList(newParent, fieldKey, child, addAfter, false);
                         newParent.treeView_Open = true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fb29fb0d3..ab718c351 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -239,9 +239,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     ScreenToLocalXf = () => this.screenToLocalXf.copy();
     getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
     isAnyChildContentActive = () => this.props.isAnyChildContentActive();
-    addLiveTextBox = (newBox: Doc) => {
-        FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
-        this.addDocument(newBox);
+    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();
@@ -1177,7 +1177,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 newDoc[layout_fieldKey] = docView.Document[layout_fieldKey];
             }
             Doc.GetProto(newDoc).text = undefined;
-            FormattedTextBox.SelectOnLoad = newDoc[Id];
+            FormattedTextBox.SetSelectOnLoad(newDoc);
             return this.addDocument?.(newDoc);
         }
     };
@@ -1204,8 +1204,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             <CollectionFreeFormDocumentViewWrapper
                 {...OmitKeys(entry, ['replica', 'pair']).omit}
                 key={childLayout[Id] + (entry.replica || '')}
-                DataDoc={childData}
                 Document={childLayout}
+                TemplateDataDocument={childData}
                 dragStarting={this.dragStarting}
                 dragEnding={this.dragEnding}
                 isGroupActive={this.props.isGroupActive}
@@ -1888,7 +1888,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         <DocumentView
                             {...this.props}
                             Document={this._lightboxDoc}
-                            DataDoc={undefined}
+                            TemplateDataDocument={undefined}
                             PanelWidth={this.lightboxPanelWidth}
                             PanelHeight={this.lightboxPanelHeight}
                             NativeWidth={returnZero}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index be806d3cc..82f0171eb 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -192,7 +192,7 @@ export class CollectionGridView extends CollectionSubView() {
                 NativeHeight={returnZero}
                 setContentView={emptyFunction}
                 Document={layout}
-                DataDoc={layout.resolvedDataDoc as Doc}
+                TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 isContentActive={this.isChildContentActive}
                 PanelWidth={width}
                 PanelHeight={height}
@@ -351,7 +351,7 @@ export class CollectionGridView extends CollectionSubView() {
                         undoBatch(
                             action(() => {
                                 const text = Docs.Create.TextDocument('', { _width: 150, _height: 50 });
-                                FormattedTextBox.SelectOnLoad = text[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+                                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);
                                 this.setLayoutList(this.addLayoutItem(this.savedLayoutList, this.makeLayoutItem(text, this.screenToCell(e.clientX, e.clientY))));
                             })
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 05ec0448b..043914985 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -253,7 +253,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         return (
             <DocumentView
                 Document={childLayout}
-                DataDoc={childLayout.resolvedDataDoc as Doc}
+                TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
                 styleProvider={this.props.styleProvider}
                 docViewPath={this.props.docViewPath}
                 LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index b4b62e635..2d4750de3 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -249,7 +249,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         return (
             <DocumentView
                 Document={layout}
-                DataDoc={layout.resolvedDataDoc as Doc}
+                TemplateDataDocument={layout.resolvedDataDoc as Doc}
                 styleProvider={this.props.styleProvider}
                 docViewPath={this.props.docViewPath}
                 LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 0b292a445..030f9953c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -892,7 +892,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                         {Array.from(this._selectedDocs).lastElement() && (
                             <DocumentView
                                 Document={Array.from(this._selectedDocs).lastElement()}
-                                DataDoc={undefined}
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
                                 onClickScriptDisable="always"
@@ -971,7 +970,6 @@ class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocPro
                 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}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 150b1b46e..bd6d3bad6 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -272,7 +272,7 @@ export class SchemaRTFCell extends React.Component<SchemaTableCellProps> {
         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 }}>
-                {this.selected ? <FormattedTextBox {...fieldProps} DataDoc={this.props.Document} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
+                {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
             </div>
         );
     }
diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx
index f02a284da..a36c8d778 100644
--- a/src/client/views/linking/LinkPopup.tsx
+++ b/src/client/views/linking/LinkPopup.tsx
@@ -61,7 +61,6 @@ export class LinkPopup extends React.Component<LinkPopupProps> {
 
                     <SearchBox
                         Document={Doc.MySearcher}
-                        DataDoc={Doc.MySearcher}
                         linkFrom={linkDoc}
                         linkCreateAnchor={this.props.linkCreateAnchor}
                         linkSearch={true}
diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx
index 22b996809..f3cde3f5a 100644
--- a/src/client/views/newlightbox/NewLightboxView.tsx
+++ b/src/client/views/newlightbox/NewLightboxView.tsx
@@ -293,7 +293,6 @@ export class NewLightboxView extends React.Component<LightboxViewProps> {
                     <DocumentView
                         ref={action((r: DocumentView | null) => (NewLightboxView._docView = r !== null ? r : undefined))}
                         Document={LightboxView.LightboxDoc}
-                        DataDoc={undefined}
                         PanelWidth={this.newLightboxWidth}
                         PanelHeight={this.newLightboxHeight}
                         LayoutTemplate={NewLightboxView.LightboxDocTemplate}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b7cd306ae..6195a155d 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -36,7 +36,7 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
     CollectionFreeFormView: CollectionFreeFormView;
 }
 @observer
-export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps {
+export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
     @observable X = this.props.x;
     @observable Y = this.props.y;
     @observable Z = this.props.z;
@@ -115,7 +115,7 @@ export interface CollectionFreeFormDocumentViewProps {
 }
 
 @observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps & { fieldKey: string }>() {
     get displayName() { // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index b050e3252..9fd4fbcaa 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -173,7 +173,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                     <DocumentView
                         {...this.props}
                         Document={targetDoc}
-                        DataDoc={undefined}
+                        TemplateDataDocument={undefined}
                         moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
                         removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
                         NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index eed787b6d..4b58bfe48 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,7 +1,8 @@
-import { computed, trace } from 'mobx';
+import { computed } from 'mobx';
 import { observer } from 'mobx-react';
+import { MouseEventHandler } from 'react-select';
 import { Doc, Opt } from '../../../fields/Doc';
-import { AclPrivate } from '../../../fields/DocSymbols';
+import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
 import { Cast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
@@ -36,6 +37,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { LinkBox } from './LinkBox';
 import { LoadingBox } from './LoadingBox';
 import { MapBox } from './MapBox/MapBox';
+import { MapPushpinBox } from './MapBox/MapPushpinBox';
 import { PDFBox } from './PDFBox';
 import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox';
 import { RecordingBox } from './RecordingBox';
@@ -46,7 +48,6 @@ import { VideoBox } from './VideoBox';
 import { WebBox } from './WebBox';
 import React = require('react');
 import XRegExp = require('xregexp');
-import { MapPushpinBox } from './MapBox/MapPushpinBox';
 
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -117,10 +118,11 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 
 @observer
 export class DocumentContentsView extends React.Component<
-    DocumentViewProps & {
-        setHeight?: (height: number) => void;
-        layout_fieldKey: string;
-    }
+    DocumentViewProps &
+        FieldViewProps & {
+            setHeight?: (height: number) => void;
+            layout_fieldKey: string;
+        }
 > {
     @computed get layout(): string {
         TraceMobx();
@@ -137,10 +139,6 @@ export class DocumentContentsView extends React.Component<
         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 dataDoc() {
-        const proto = this.props.DataDoc || Doc.GetProto(this.props.Document);
-        return proto instanceof Promise ? undefined : proto;
-    }
     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);
@@ -163,19 +161,24 @@ export class DocumentContentsView extends React.Component<
             'LayoutTemplate',
             'dontCenter',
             'contextMenuItems',
-            'onClick',
+            //'onClick', // don't need to omit this since it will be set
             'onDoubleClick',
             'onPointerDown',
             'onPointerUp',
         ];
-        const list = {
-            ...OmitKeys(this.props, [...docOnlyProps], '').omit,
-            Document: this.layoutDoc,
-            DataDoc: this.dataDoc,
-            onClick: onClick,
-            onInput: onInput,
+        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,
+            TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc,
+            onClick: onClick as any as 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,
+        };
+        return {
+            props: {
+                ...OmitKeys(list, [...docOnlyProps], '').omit,
+            },
         };
-        return { props: list };
     }
 
     // componentWillUpdate(oldProps: any, newState: any) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b21b22cd2..977a9bc54 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,7 +5,7 @@ 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 } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -109,7 +109,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>, property: string) => any;
+export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
     updateIcon?: () => void; // updates the icon representation of the document
     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)
@@ -150,11 +150,10 @@ export interface DocComponentView {
 }
 // These props are passed to both FieldViews and DocumentViews
 export interface DocumentViewSharedProps {
-    fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps.  In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
     DocumentView?: () => DocumentView;
     renderDepth: number;
     Document: Doc;
-    DataDoc?: Doc;
+    TemplateDataDocument?: Doc;
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     suppressSetHeight?: boolean;
@@ -506,7 +505,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     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.Document.rootDocument) && !(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;
@@ -548,7 +547,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._downTime = Date.now();
-        if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(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 (
@@ -814,7 +813,7 @@ 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.DataDoc), 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))) {
@@ -898,7 +897,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     };
 
     @computed get _rootSelected() {
-        return this.props.isSelected() || BoolCast(this.Document.rootDocument && this.props.rootSelected?.());
+        return this.props.isSelected() || BoolCast(this.props.TemplateDataDocument && this.props.rootSelected?.());
     }
     rootSelected = () => this._rootSelected;
     panelHeight = () => this.props.PanelHeight() - this.headerMargin;
@@ -938,6 +937,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 <DocumentContentsView
                     key={1}
                     {...this.props}
+                    fieldKey=""
                     pointerEvents={this.contentPointerEvents}
                     docViewPath={this.props.viewPath}
                     setContentView={this.setContentView}
@@ -1462,10 +1462,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
     }
     @computed get nativeWidth() {
-        return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth));
+        return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
     }
     @computed get nativeHeight() {
-        return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth));
+        return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.TemplateDataDocument, !this.layout_fitWidth));
     }
     @computed get shouldNotScale() {
         return this.props.shouldNotScale?.() || (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index f7f94c546..219e3025a 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -16,7 +16,6 @@ import { DocumentViewSharedProps } from './DocumentView';
 export interface FieldViewProps extends DocumentViewSharedProps {
     // FieldView specific props that are not part of DocumentView props
     fieldKey: string;
-    scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further
 
     select: (isCtrlPressed: boolean) => void;
     isContentActive: (outsideReaction?: boolean) => boolean | undefined;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 0c360acf0..8f6550663 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -13,7 +13,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoable, UndoManager } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
-import { DocComponent } from '../../DocComponent';
+import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { SelectedDocView } from '../../selectedDoc';
 import { StyleProp } from '../../StyleProvider';
@@ -41,7 +41,7 @@ export interface ButtonProps extends FieldViewProps {
     type?: ButtonType;
 }
 @observer
-export class FontIconBox extends DocComponent<ButtonProps>() {
+export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(FontIconBox, fieldKey);
     }
@@ -130,7 +130,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 type = 'slider';
                 break;
         }
-        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self:this.Document,value, _readOnly_: value === undefined });
+        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);
         // Script for checking the outcome of the toggle
         const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
@@ -157,7 +157,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             this,
             e,
             (e: PointerEvent) => {
-                return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self:this.Document,value: { doc: value, e } }).result;
+                return ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result;
             },
             emptyFunction,
             emptyFunction
@@ -205,7 +205,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             }
             noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
         } else {
-            text = script?.script.run({ this: this.Document, self:this.Document,value: '', _readOnly_: true }).result;
+            text = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
             // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
             getStyle = (val: string) => ({ fontFamily: val });
         }
@@ -223,7 +223,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         return (
             <Dropdown
                 selectedVal={text}
-                setSelectedVal={undoable(value => script.script.run({ this: this.Document, self:this.Document,value }), `dropdown select ${this.label}`)}
+                setSelectedVal={undoable(value => script.script.run({ this: this.Document, self: this.Document, value }), `dropdown select ${this.label}`)}
                 color={SettingsManager.userColor}
                 background={SettingsManager.userVariantColor}
                 type={Type.TERT}
@@ -247,17 +247,17 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
      */
     @computed get colorButton() {
         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 curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent';
         const tooltip: string = StrCast(this.Document.toolTip);
 
         return (
             <ColorPicker
                 setSelectedColor={value => {
                     if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
-                    this.colorScript?.script.run({ this: this.Document, self:this.Document, value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
                 }}
                 setFinalColor={value => {
-                    this.colorScript?.script.run({ this: this.Document, self:this.Document,value: value, _readOnly_: false });
+                    this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false });
                     this.colorBatch?.end();
                     this.colorBatch = undefined;
                 }}
@@ -277,7 +277,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const tooltip: string = StrCast(this.Document.toolTip);
 
         const script = ScriptCast(this.Document.onClick);
-        const toggleStatus = script ? script.script.run({ this: this.Document, self:this.Document, value: undefined, _readOnly_: true }).result : false;
+        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 items = DocListCast(this.dataDoc.data);
@@ -308,7 +308,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         const tooltip = StrCast(this.Document.toolTip);
 
         const script = ScriptCast(this.Document.onClick);
-        const toggleStatus = script ? script.script.run({ this: this.Document, self:this.Document,value: undefined, _readOnly_: true }).result : false;
+        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);
@@ -324,7 +324,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 //background={SettingsManager.userBackgroundColor}
                 icon={this.Icon(color)!}
                 label={this.label}
-                onPointerDown={() => script.script.run({ this: this.Document, self:this.Document, value: !toggleStatus, _readOnly_: false })}
+                onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })}
             />
         );
     }
@@ -344,9 +344,9 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         // Script for running the toggle
         const script = ScriptCast(this.Document.script);
         // Function to run the script
-        const checkResult = script?.script.run({ this: this.Document, self:this.Document,value: '', _readOnly_: true }).result;
+        const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result;
 
-        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, self:this.Document,value, _readOnly_: false }).result;
+        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result;
 
         return <EditableText editing={false} setEditing={(editing: boolean) => {}} />;
 
@@ -354,7 +354,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             <div className="menuButton editableText">
                 <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
                 <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
-                    <EditableView GetValue={() => script?.script.run({ this: this.Document, self:this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
+                    <EditableView GetValue={() => script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 39e0279fc..577685636 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -58,7 +58,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
     render() {
         const props: FieldViewProps = {
             Document: this.props.doc,
-            DataDoc: this.props.doc,
             childFilters: returnEmptyFilter,
             childFiltersByRanges: returnEmptyFilter,
             searchFilterDocs: returnEmptyDoclist,
diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx
index 86c91ae3c..01dd830f8 100644
--- a/src/client/views/nodes/LoadingBox.tsx
+++ b/src/client/views/nodes/LoadingBox.tsx
@@ -78,7 +78,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <div className="loadingBox-textContainer">
                     <span className="loadingBox-title">{StrCast(this.Document.title)}</span>
                     <p className="loadingBox-headerText">{StrCast(this.Document.loadingError, 'Loading ' + (this.progress.replace('[download]', '') || '(can take several minutes)'))}</p>
-                    {this.rootDDocumentoc.loadingError ? null : (
+                    {this.Document.loadingError ? null : (
                         <div className="loadingBox-spinner">
                             <ReactLoading type="spinningBubbles" color="blue" height={100} width={100} />
                         </div>
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 14a8a387a..398d1255e 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -228,7 +228,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         const targetCreator = (annotationOn: Doc | undefined) => {
             const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
-            FormattedTextBox.SelectOnLoad = target[Id];
+            FormattedTextBox.SetSelectOnLoad(target);
             return target;
         };
         const docView = this.props.DocumentView?.();
@@ -774,7 +774,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                           {...this.props}
                                           renderDepth={this.props.renderDepth + 1}
                                           Document={pushpin}
-                                          DataDoc={undefined}
+                                          TemplateDataDocument={undefined}
                                           PanelWidth={returnOne}
                                           PanelHeight={returnOne}
                                           NativeWidth={returnOne}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 9608e2dd0..3713e93ce 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -32,9 +32,9 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
 
     addNoteClick = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => {
-            const newBox = Docs.Create.TextDocument('Note', { _layout_autoHeight: true });
-            FormattedTextBox.SelectOnLoad = newBox[Id]; // 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.place, 'data', newBox);
+            const newDoc = Docs.Create.TextDocument('Note', { _layout_autoHeight: true });
+            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
+            Doc.AddDocToList(this.props.place, 'data', newDoc);
             this._stack?.scrollToBottom();
             e.stopPropagation();
             e.preventDefault();
@@ -47,9 +47,9 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
     removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
     render() {
         return (
-            <InfoWindow 
-            // anchor={this.props.markerMap[this.props.place[Id]]} 
-            onCloseClick={this.handleInfoWindowClose}>
+            <InfoWindow
+                // anchor={this.props.markerMap[this.props.place[Id]]}
+                onCloseClick={this.handleInfoWindowClose}>
                 <div className="mapbox-infowindow">
                     <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
                         <CollectionStackingView
@@ -57,7 +57,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             {...this.props}
                             setContentView={emptyFunction}
                             Document={this.props.place}
-                            DataDoc={undefined}
+                            TemplateDataDocument={undefined}
                             fieldKey="data"
                             NativeWidth={returnZero}
                             NativeHeight={returnZero}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 6caef0daa..c2e204eab 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -37,7 +37,7 @@ declare class MediaRecorder {
 //     setRaised: (r: { coord: Vector2, off: Vector3 }[]) => void;
 //     x: number;
 //     y: number;
-//     rootDoc: Doc;
+//     doc: Doc;
 //     color: string;
 // }
 
@@ -69,8 +69,8 @@ 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.rootDoc.popOut);
-//         const popOff = () => NumCast(this.props.rootDoc.popOff);
+//         const popOut = () => NumCast(this.props.Document.popOut);
+//         const popOff = () => NumCast(this.props.Document.popOff);
 //         return (
 //             <mesh key={`mesh${topLeft[0]}${topLeft[1]}`} onClick={action(async e => {
 //                 this.props.setRaised([
@@ -207,7 +207,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         //     const colors = ["yellow", "red", "orange", "brown", "maroon", "gray"];
         //     let count = 0;
         //     numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push(
-        //         <VideoTile rootDoc={this.layoutDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />)));
+        //         <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 => {
         //         this._camera = props.camera;
         //         props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 8a927560d..42e8ace6e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -15,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, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -190,7 +190,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     private gptRes: string = '';
 
     public static PasteOnLoad: ClipboardEvent | undefined;
-    public static SelectOnLoad = '';
+    private static SelectOnLoad: Doc | undefined;
+    public static SetSelectOnLoad(doc: Doc) {
+        FormattedTextBox.SelectOnLoad = doc;
+    }
     public static DontSelectInitialText = false; // whether initial text should be selected or not
     public static SelectOnLoadChar = '';
     public static IsFragment(html: string) {
@@ -311,7 +314,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             e.stopPropagation();
             const targetCreator = (annotationOn?: Doc) => {
                 const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn);
-                FormattedTextBox.SelectOnLoad = target[Id];
+                FormattedTextBox.SetSelectOnLoad(target);
                 return target;
             };
 
@@ -1511,10 +1514,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             (this._editorView as any).TextView = this;
         }
 
-        const selectOnLoad = DocCast(this.Document.rootDocument, this.Document)[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()));
+        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 = '';
+            FormattedTextBox.SelectOnLoad = undefined;
             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;
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index ec11079b4..08bad2d57 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -47,7 +47,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     }
 
     const canEdit = (state: any) => {
-        switch (GetEffectiveAcl(props.DataDoc)) {
+        switch (GetEffectiveAcl(props.TemplateDataDocument)) {
             case AclAugment:
                 const prevNode = state.selection.$cursor.nodeBefore;
                 const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid;
@@ -334,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
 
     //Command to create a blank space
     bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => {
-        if (props.DataDoc && GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true;
+        if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true;
         const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
         dispatch(splitMetadata(marks, state.tr));
         return false;
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 170626cd2..c4c89d8b1 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -24,7 +24,6 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     LayoutTemplateString={undefined}
                     Document={this.Document}
                     isContentActive={returnFalse}
-                    DataDoc={undefined}
                     addDocument={returnFalse}
                     ScreenToLocalTransform={this.screenToLocalXf}
                     hideResizeHandles={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index b104e8b70..f8aebd021 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -56,7 +56,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 Cast(this.Document.rootDocument, Doc, null) || this.props.Document;
+        return this.props.TemplateDataDocument ?? this.props.Document;
     }
 
     // this is the document in the workspaces that is targeted by the slide
@@ -104,7 +104,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
             <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
                     Document={PresBox.targetRenderedDoc(this.slideDoc)}
-                    DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
                     PanelWidth={this.embedWidth}
                     PanelHeight={this.embedHeight}
                     isContentActive={this.props.isContentActive}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 1dfcf174e..7f71ee678 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -100,7 +100,6 @@ export class TopBar extends React.Component {
             <div className="collectionMenu-contMenuButtons" style={{ height: '100%' }}>
                 <CollectionLinearView
                     Document={selDoc}
-                    DataDoc={undefined}
                     fieldKey="data"
                     dropAction="embed"
                     setHeight={returnFalse}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 24c18c232..525ff7ec0 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -751,7 +751,7 @@ export namespace Doc {
             });
     }
 
-    const _pendingMap: Map<string, boolean> = new Map();
+    const _pendingMap = new Set<string>();
     //
     // Returns an expanded template layout for a target data document if there is a template relationship
     // between the two. If so, the layoutDoc is expanded into a new document that inherits the properties
@@ -773,19 +773,19 @@ export namespace Doc {
 
         if (templateLayoutDoc.resolvedDataDoc instanceof Promise) {
             expandedTemplateLayout = undefined;
-            _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
-        } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey)) {
-            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc))) {
+            _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
+        } else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) {
+            if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) {
                 expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params
             } else {
                 templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype
                 if (!targetDoc[expandedLayoutFieldKey]) {
-                    _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true);
+                    _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
                     setTimeout(
                         action(() => {
                             const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']');
-                            newLayoutDoc.rootDocument = targetDoc;
                             const dataDoc = Doc.GetProto(targetDoc);
+                            newLayoutDoc.rootDocument = targetDoc;
                             newLayoutDoc.embedContainer = targetDoc;
                             newLayoutDoc.resolvedDataDoc = dataDoc;
                             newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 791ce8d8f..bdd657575 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -384,7 +384,6 @@ export class MobileInterface extends React.Component {
             <div style={{ position: 'relative', top: '198px', height: `calc(100% - 350px)`, width: '100%', left: '0%' }}>
                 <DocumentView
                     Document={this.mainContainer}
-                    DataDoc={undefined}
                     addDocument={returnFalse}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
-- 
cgit v1.2.3-70-g09d2


From 678abdfceaa25cb84c4232f44a7c161b6d39b546 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 1 Dec 2023 12:17:10 -0500
Subject: cleaning up documentview props.   got rid of special cases for
 keyValueBox.  made fitToContents work cleanly for native scaled docs.

---
 src/client/views/DocumentDecorations.tsx           |  3 -
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../views/collections/CollectionCarousel3DView.tsx |  1 -
 .../views/collections/CollectionCarouselView.tsx   | 36 +++++------
 .../views/collections/CollectionNoteTakingView.tsx |  5 +-
 .../collections/CollectionStackedTimeline.tsx      |  3 +-
 .../views/collections/CollectionStackingView.tsx   |  5 +-
 src/client/views/collections/CollectionView.tsx    | 14 ++--
 src/client/views/collections/TreeView.tsx          |  7 +-
 .../CollectionFreeFormLinkView.tsx                 |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 21 +++---
 .../collectionGrid/CollectionGridView.tsx          |  1 -
 .../CollectionMulticolumnView.tsx                  |  5 +-
 .../CollectionMultirowView.tsx                     |  5 +-
 src/client/views/nodes/DocumentView.tsx            | 75 ++++++++--------------
 src/client/views/nodes/KeyValueBox.tsx             |  3 +-
 src/client/views/nodes/LabelBox.tsx                |  4 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |  4 +-
 20 files changed, 84 insertions(+), 120 deletions(-)

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 719055149..b9fcf5360 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -611,9 +611,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     get selectionTitle(): string {
         if (SelectionManager.Views().length === 1) {
             const selected = SelectionManager.Views()[0];
-            if (selected.ComponentView?.getTitle?.()) {
-                return selected.ComponentView.getTitle();
-            }
             if (this._titleControlString.startsWith('=')) {
                 return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.Document, this: selected.layoutDoc }, console.log).result?.toString() || '';
             }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2e3817cb6..58c1570b2 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -609,7 +609,7 @@ export class MainView extends React.Component {
                     isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                     isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                     ScreenToLocalTransform={this.headerBarScreenXf}
-                    childHideResizeHandles={returnTrue}
+                    childHideResizeHandles={true}
                     childDragAction="move"
                     dontRegisterView={true}
                     hideResizeHandles={true}
@@ -988,7 +988,7 @@ export class MainView extends React.Component {
                 isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events)
                 isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
                 ScreenToLocalTransform={Transform.Identity}
-                childHideResizeHandles={returnTrue}
+                childHideResizeHandles={true}
                 childDragAction="move"
                 dontRegisterView={true}
                 PanelWidth={this.headerBarDocWidth}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 8e62ed999..049ba4841 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -248,7 +248,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         isAnyChildContentActive={returnFalse}
                         childDocumentsActive={this.props.isContentActive}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                        childHideDecorationTitle={returnTrue}
+                        childHideDecorationTitle={true}
                         removeDocument={this.removeDocument}
                         moveDocument={this.moveDocument}
                         addDocument={this.addDocument}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2dd58fa80..806c9c001 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?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.();
+            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';
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 6715e5675..d6368464a 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -66,7 +66,6 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                     //suppressSetHeight={true}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
-                    shouldNotScale={undefined}
                     layout_fitWidth={undefined}
                     onDoubleClick={this.onChildDoubleClick}
                     renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index c873ef891..299a4d5d3 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
-import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
@@ -51,7 +51,7 @@ export class CollectionCarouselView extends CollectionSubView() {
         const index = NumCast(this.layoutDoc._carousel_index);
         const curDoc = this.childLayoutPairs?.[index];
         const captionProps = { ...this.props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
-        const show_captions = StrCast(this.layoutDoc._layout_showCaption);
+        const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
         return !(curDoc?.layout instanceof Doc) ? null : (
             <>
                 <div className="collectionCarouselView-image" key="image">
@@ -59,36 +59,36 @@ export class CollectionCarouselView extends CollectionSubView() {
                         {...this.props}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
-                        shouldNotScale={undefined}
                         layout_fitWidth={undefined}
                         setContentView={undefined}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
                         isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.props.isContentActive}
                         isContentActive={this.props.childContentsActive ?? this.props.isContentActive() === false ? returnFalse : emptyFunction}
-                        hideCaptions={show_captions ? true : false}
+                        hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
                         renderDepth={this.props.renderDepth + 1}
                         LayoutTemplate={this.props.childLayoutTemplate}
                         LayoutTemplateString={this.props.childLayoutString}
                         Document={curDoc.layout}
-                        TemplateDataDocument={curDoc.layout.resolvedDataDoc as Doc}
+                        TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)}
                         PanelHeight={this.panelHeight}
                         bringToFront={returnFalse}
                     />
                 </div>
-                <div
-                    className="collectionCarouselView-caption"
-                    key="caption"
-                    onWheel={StopEvent}
-                    style={{
-                        display: show_captions ? undefined : 'none',
-                        borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
-                        marginRight: this.marginX,
-                        marginLeft: this.marginX,
-                        width: `calc(100% - ${this.marginX * 2}px)`,
-                    }}>
-                    <FormattedTextBox key={index} {...captionProps} fieldKey={show_captions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
-                </div>
+                {!carouselShowsCaptions ? null : (
+                    <div
+                        className="collectionCarouselView-caption"
+                        key="caption"
+                        onWheel={StopEvent}
+                        style={{
+                            borderRadius: this.props.styleProvider?.(this.layoutDoc, captionProps, StyleProp.BorderRounding),
+                            marginRight: this.marginX,
+                            marginLeft: this.marginX,
+                            width: `calc(100% - ${this.marginX * 2}px)`,
+                        }}>
+                        <FormattedTextBox key={index} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
+                    </div>
+                )}
             </>
         );
     }
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index e92cb353f..ad5431c8e 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -257,9 +257,8 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 ScreenToLocalTransform={noteTakingDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
-                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
-                hideResizeHandles={this.props.childHideResizeHandles?.()}
-                hideTitle={this.props.childHideTitle?.()}
+                hideDecorationTitle={this.props.childHideDecorationTitle}
+                hideResizeHandles={this.props.childHideResizeHandles}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 addDocument={this.props.addDocument}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 57ea777e4..7fee1cda6 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -17,7 +17,6 @@ import { DragManager } from '../../util/DragManager';
 import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
@@ -35,6 +34,7 @@ export type CollectionStackedTimelineProps = {
     playLink: (linkDoc: Doc, options: DocFocusOptions) => void;
     playFrom: (seekTimeInSeconds: number, endTime?: number) => void;
     playing: () => boolean;
+    thumbnails?: () => string[];
     setTime: (time: number) => void;
     startTag: string;
     endTag: string;
@@ -43,7 +43,6 @@ export type CollectionStackedTimelineProps = {
     rawDuration: number;
     dataFieldKey: string;
     fieldKey: string;
-    thumbnails?: () => string[];
 };
 
 // trimming state: shows full clip, current trim bounds, or not trimming
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 10ee01e0b..59e0c60b9 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -349,9 +349,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 ScreenToLocalTransform={stackedDocTransform}
                 focus={this.focusDocument}
                 childFilters={this.childDocFilters}
-                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
-                hideResizeHandles={this.props.childHideResizeHandles?.()}
-                hideTitle={this.props.childHideTitle?.()}
+                hideDecorationTitle={this.props.childHideDecorationTitle}
+                hideResizeHandles={this.props.childHideResizeHandles}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
                 xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 898b453f4..2e4c5af6b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -11,7 +11,6 @@ import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
 import { dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
-import { InteractionUtils } from '../../util/InteractionUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -50,10 +49,9 @@ interface CollectionViewProps_ extends FieldViewProps {
     childlayout_showTitle?: () => string;
     childOpacity?: () => number;
     childContextMenuItems?: () => { script: ScriptField; label: string }[];
-    childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
-    childHideDecorationTitle?: () => boolean;
-    childHideResizeHandles?: () => boolean;
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childHideDecorationTitle?: boolean;
+    childHideResizeHandles?: boolean;
     childDragAction?: dropActionType;
     childXPadding?: number;
     childYPadding?: number;
@@ -230,8 +228,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
 
     bodyPanelWidth = () => this.props.PanelWidth();
 
-    childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles);
-    childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.Document.childLayoutTemplate, Doc, null);
     isContentActive = (outsideReaction?: boolean) => this._isContentActive;
 
@@ -244,14 +240,14 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             removeDocument: this.removeDocument,
             isContentActive: this.isContentActive,
             isAnyChildContentActive: this.isAnyChildContentActive,
-            whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
             PanelWidth: this.bodyPanelWidth,
             PanelHeight: this.props.PanelHeight,
             ScreenToLocalTransform: this.screenToLocalTransform,
             childLayoutTemplate: this.childLayoutTemplate,
+            whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
             childLayoutString: StrCast(this.Document.childLayoutString, this.props.childLayoutString),
-            childHideResizeHandles: this.childHideResizeHandles,
-            childHideDecorationTitle: this.childHideDecorationTitle,
+            childHideResizeHandles: this.props.childHideResizeHandles ?? BoolCast(this.Document.childHideResizeHandles),
+            childHideDecorationTitle: this.props.childHideDecorationTitle ?? BoolCast(this.Document.childHideDecorationTitle),
         };
         return (
             <div
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a756007f8..ba4ed6074 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -199,10 +199,10 @@ export class TreeView extends React.Component<TreeViewProps> {
         this._disposers.selection?.();
         if (!docView) {
             this._editTitle = false;
-        } else if (docView.isSelected()) {
+        } else if (docView.SELECTED) {
             this._editTitle = true;
             this._disposers.selection = reaction(
-                () => docView.isSelected(),
+                () => docView.SELECTED,
                 isSel => !isSel && this.setEditTitle(undefined)
             );
         } else {
@@ -981,9 +981,8 @@ export class TreeView extends React.Component<TreeViewProps> {
                 moveDocument={this.move}
                 removeDocument={this.props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
-                NativeHeight={return18}
+                NativeHeight={returnZero}
                 NativeWidth={returnZero}
-                shouldNotScale={returnTrue}
                 PanelWidth={this.titleWidth}
                 PanelHeight={return18}
                 contextMenuItems={this.contextMenuItems}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 5e0cf1d23..0c0d45a30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -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.Document[Brushed];
-        const bActive = B.isSelected() || B.Document[Brushed];
+        const aActive = A.SELECTED || A.Document[Brushed];
+        const bActive = B.SELECTED || 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 ab718c351..d242b1356 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -122,12 +122,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return renderableEles;
     }
     @computed get fitToContentVals() {
+        const hgt = this.contentBounds.b - this.contentBounds.y;
+        const wid = this.contentBounds.r - this.contentBounds.x;
         return {
-            bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
+            bounds: { ...this.contentBounds, cx: this.contentBounds.x + wid / 2, cy: this.contentBounds.y + hgt / 2 },
             scale:
-                !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
-                    ? 1
-                    : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+                (!this.childDocs.length || !Number.isFinite(hgt) || !Number.isFinite(wid)
+                    ? 1 //
+                    : Math.min(this.props.PanelHeight() / hgt, this.props.PanelWidth() / wid)) / (this.props.NativeDimScaling?.() || 1),
         };
     }
     @computed get fitContentsToBox() {
@@ -144,19 +146,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
               );
     }
     @computed get nativeWidth() {
-        return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : 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?.() || (this.fitContentsToBox ? 0 : 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.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+        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
     }
     @computed get cachedCenteringShiftY(): number {
         const dv = this.props.DocumentView?.();
         const fitWidth = this.props.layout_fitWidth?.(this.Document) ?? dv?.layoutDoc.layout_fitWidth;
-        const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
+        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
@@ -221,14 +223,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
     fitContentOnce = () => {
-        if (this.props.DocumentView?.().nativeWidth) return;
         const vals = this.fitToContentVals;
         this.layoutDoc._freeform_panX = vals.bounds.cx;
         this.layoutDoc._freeform_panY = vals.bounds.cy;
         this.layoutDoc._freeform_scale = vals.scale;
     };
     freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
-    ignoreNativeDimScaling = () => (this.fitContentsToBox ? true : false);
     // freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
     panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
@@ -1244,7 +1244,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 layout_showTitle={this.props.childlayout_showTitle}
                 dontRegisterView={this.props.dontRegisterView}
                 pointerEvents={this.childPointerEventsFunc}
-                //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this
             />
         );
     }
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 82f0171eb..8daed9746 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -187,7 +187,6 @@ export class CollectionGridView extends CollectionSubView() {
         return (
             <DocumentView
                 {...this.props}
-                shouldNotScale={returnFalse}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 setContentView={emptyFunction}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 043914985..e7af91390 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -261,7 +261,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
-                shouldNotScale={shouldNotScale}
                 rootSelected={this.rootSelected}
                 dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
@@ -270,8 +269,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={childLayout.layout_fitWidth || this.props.childHideResizeHandles?.() ? true : false}
-                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+                hideResizeHandles={childLayout.layout_fitWidth || this.props.childHideResizeHandles ? true : false}
+                hideDecorationTitle={this.props.childHideDecorationTitle}
                 fitContentsToBox={this.props.fitContentsToBox}
                 focus={this.props.focus}
                 childFilters={this.childDocFilters}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 2d4750de3..ef66e8ce5 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -257,7 +257,6 @@ export class CollectionMultirowView extends CollectionSubView() {
                 renderDepth={this.props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
-                shouldNotScale={shouldNotScale}
                 rootSelected={this.rootSelected}
                 dropAction={StrCast(this.Document.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
@@ -265,8 +264,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                 ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
-                hideResizeHandles={layout.layout_fitWidth || this.props.childHideResizeHandles?.() ? true : false}
-                hideDecorationTitle={this.props.childHideDecorationTitle?.()}
+                hideResizeHandles={layout.layout_fitWidth || this.props.childHideResizeHandles ? true : false}
+                hideDecorationTitle={this.props.childHideDecorationTitle}
                 fitContentsToBox={this.props.fitContentsToBox}
                 dragAction={this.props.childDragAction}
                 focus={this.props.focus}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 977a9bc54..bfbe3389f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -111,6 +111,8 @@ export interface DocFocusOptions {
 export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
 export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any;
 export interface DocComponentView {
+    fieldKey?: string;
+    annotationKey?: string;
     updateIcon?: () => void; // updates the icon representation of the document
     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;
@@ -119,10 +121,8 @@ export interface DocComponentView {
     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)
-    ignoreNativeDimScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
-    menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -136,11 +136,6 @@ export interface DocComponentView {
     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)
-    fieldKey?: string;
-    annotationKey?: string;
-    getTitle?: () => string;
     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 };
@@ -150,28 +145,24 @@ export interface DocComponentView {
 }
 // These props are passed to both FieldViews and DocumentViews
 export interface DocumentViewSharedProps {
-    DocumentView?: () => DocumentView;
     renderDepth: number;
     Document: Doc;
     TemplateDataDocument?: Doc;
+    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+    DocumentView?: () => DocumentView;
+    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
-    suppressSetHeight?: boolean;
     setContentView?: (view: DocComponentView) => any;
-    CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
     PanelWidth: () => number;
     PanelHeight: () => number;
-    shouldNotScale?: () => boolean;
     docViewPath: () => DocumentView[];
-    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.
+    childFilters: () => string[];
+    childFiltersByRanges: () => string[];
     styleProvider: Opt<StyleProviderFunc>;
     setTitleFocus?: () => void;
     focus: DocFocusFunc;
     layout_fitWidth?: (doc: Doc) => boolean | undefined;
-    childFilters: () => string[];
-    childFiltersByRanges: () => string[];
     searchFilterDocs: () => Doc[];
     layout_showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
@@ -184,26 +175,27 @@ export interface DocumentViewSharedProps {
     pinToPres: (document: Doc, pinProps: PinProps) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
-    dragAction?: dropActionType;
+    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
+    pointerEvents?: () => Opt<string>;
     treeViewDoc?: Doc;
     xPadding?: number;
     yPadding?: number;
-    dropAction?: dropActionType;
     dontRegisterView?: boolean;
+    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.
+    dropAction?: dropActionType;
+    dragAction?: dropActionType;
     dragWhenActive?: boolean;
+    dontHideOnDrag?: boolean;
     hideLinkButton?: boolean;
     hideCaptions?: boolean;
     ignoreAutoHeight?: boolean;
     forceAutoHeight?: boolean;
+    suppressSetHeight?: boolean;
     disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
     onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
-    waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
-    defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
-    pointerEvents?: () => Opt<string>;
-    scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
-    createNewFilterDoc?: () => void;
-    updateFilterDoc?: (doc: Doc) => void;
-    dontHideOnDrag?: boolean;
 }
 
 // these props are specific to DocuentViews
@@ -217,12 +209,11 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
     hideOpenButton?: boolean;
     hideDeleteButton?: boolean;
     hideLinkAnchors?: boolean;
-    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
-    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     contentPointerEvents?: 'none' | 'all' | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
-    radialMenu?: String[];
     LayoutTemplateString?: string;
     dontCenter?: 'x' | 'y' | 'xy';
+    isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+    isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
     NativeWidth?: () => number;
     NativeHeight?: () => number;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
@@ -240,8 +231,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
 
 // these props are only available in DocumentViewIntenral
 export interface DocumentViewInternalProps extends DocumentViewProps {
-    NativeWidth: () => number;
-    NativeHeight: () => number;
     isSelected: () => boolean;
     select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
     DocumentView: () => DocumentView;
@@ -325,12 +314,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @computed get finalLayoutKey() {
         return StrCast(this.Document.layout_fieldKey, 'layout');
     }
-    @computed get nativeWidth() {
-        return this.props.NativeWidth();
-    }
-    @computed get nativeHeight() {
-        return this.props.NativeHeight();
-    }
     @computed get disableClickScriptFunc() {
         const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
         // prettier-ignore
@@ -903,7 +886,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     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.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;
 
@@ -943,12 +926,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     setContentView={this.setContentView}
                     childFilters={this.childFilters}
                     PanelHeight={this.panelHeight}
-                    setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
+                    setHeight={this.setHeight}
                     isContentActive={this.isContentActive}
                     ScreenToLocalTransform={this.screenToLocal}
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
-                    focus={this.props.focus}
                     setTitleFocus={this.setTitleFocus}
                     layout_fieldKey={this.finalLayoutKey}
                 />
@@ -1443,15 +1425,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     @computed get layout_fitWidth() {
-        return this.docView?._componentView?.layout_fitWidth?.() ?? 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;
     }
     @computed get hideLinkButton() {
-        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : ''));
+        return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.SELECTED ? ':selected' : ''));
     }
-    hideLinkCount = () => this.props.renderDepth === -1 || (this.isSelected() && 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} />;
     }
@@ -1462,13 +1444,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return Doc.Layout(this.Document, this.props.LayoutTemplate?.());
     }
     @computed get nativeWidth() {
-        return this.docView?._componentView?.ignoreNativeDimScaling?.() ? 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.docView?._componentView?.ignoreNativeDimScaling?.() ? 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.props.shouldNotScale?.() || (this.layout_fitWidth && !this.nativeWidth) || [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);
@@ -1589,7 +1571,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     docViewPathFunc = () => this.docViewPath;
     isSelected = () => this.SELECTED;
     select = (extendSelection: boolean, focusSelection?: boolean) => {
-        if (this.isSelected() && SelectionManager.Views().length > 1) SelectionManager.DeselectView(this);
+        if (this.SELECTED && SelectionManager.Views().length > 1) SelectionManager.DeselectView(this);
         else {
             SelectionManager.SelectView(this, extendSelection);
             if (focusSelection) {
@@ -1684,7 +1666,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             {...this.props}
                             DocumentView={this.selfView}
                             viewPath={this.docViewPathFunc}
-                            shouldNotScale={this.ShouldNotScale}
                             PanelWidth={this.PanelWidth}
                             PanelHeight={this.PanelHeight}
                             NativeWidth={this.NativeWidth}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 95d2a2667..0c6377c3a 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -41,10 +41,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     componentDidMount() {
         this.props.setContentView?.(this);
     }
-    ignoreNativeDimScaling = returnTrue;
+    isKeyValueBox = returnTrue;
     able = returnAlways;
     layout_fitWidth = returnTrue;
-    overridePointerEvents = returnAll;
     onClickScriptDisable = returnAlways;
 
     @observable private rows: KeyValuePair[] = [];
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 0d7b2b0a4..e1421878b 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -36,7 +36,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         this._timeout && clearTimeout(this._timeout);
     }
 
-    getTitle() {
+    @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);
     }
 
@@ -119,7 +119,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
         const params = Cast(this.paramsDoc['onClick-paramFieldKeys'], listSpec('string'), []);
         const missingParams = params?.filter(p => !this.paramsDoc[p]);
         params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
-        const label = this.getTitle();
+        const label = this.Title;
         return (
             <div
                 className="labelBox-outerDiv"
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 3713e93ce..415a9d776 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -68,8 +68,8 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                             NativeDimScaling={returnOne}
                             isContentActive={returnTrue}
                             chromeHidden={true}
-                            childHideResizeHandles={returnTrue}
-                            childHideDecorationTitle={returnTrue}
+                            childHideResizeHandles={true}
+                            childHideDecorationTitle={true}
                             childLayoutFitWidth={this.childLayoutFitWidth}
                             // childDocumentsActive={returnFalse}
                             removeDocument={this.removeDoc}
-- 
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/collections/collectionGrid/CollectionGridView.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 1cf241544f8063e3d71406238a584299b6ced794 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 13 Dec 2023 21:17:50 -0500
Subject: cleaned up props/_props handling by inherting from
 ObservableReactComponent

---
 .vscode/settings.json                              |  4 +-
 src/Utils.ts                                       |  7 ++-
 src/client/util/DocumentManager.ts                 |  3 +-
 src/client/util/SelectionManager.ts                | 22 +++----
 src/client/views/AntimodeMenu.tsx                  | 13 +----
 src/client/views/ContextMenu.tsx                   |  5 +-
 src/client/views/ContextMenuItem.tsx               | 15 ++---
 src/client/views/DashboardView.tsx                 |  4 +-
 src/client/views/DocComponent.tsx                  | 43 ++++++--------
 src/client/views/DocumentButtonBar.tsx             |  8 +--
 src/client/views/DocumentDecorations.tsx           |  6 +-
 src/client/views/EditableView.tsx                  | 14 ++---
 src/client/views/MainView.tsx                      | 47 +++++++--------
 src/client/views/MarqueeAnnotator.tsx              | 20 +++----
 src/client/views/ObservableReactComponent.tsx      | 21 +++++++
 src/client/views/OverlayView.tsx                   | 18 ++----
 src/client/views/PreviewCursor.tsx                 | 13 +++--
 src/client/views/PropertiesDocContextSelector.tsx  | 17 ++----
 src/client/views/PropertiesView.tsx                | 26 ++++-----
 src/client/views/ScriptingRepl.tsx                 | 29 +++-------
 src/client/views/SidebarAnnos.tsx                  | 25 ++++----
 .../views/collections/CollectionCarousel3DView.tsx |  6 +-
 .../views/collections/CollectionCarouselView.tsx   |  7 ++-
 .../views/collections/CollectionDockingView.tsx    | 15 ++---
 .../collections/CollectionMasonryViewFieldRow.tsx  | 18 +++---
 src/client/views/collections/CollectionMenu.tsx    |  2 +-
 .../views/collections/CollectionNoteTakingView.tsx |  8 ++-
 .../views/collections/CollectionPileView.tsx       |  7 ++-
 .../collections/CollectionStackedTimeline.tsx      | 22 ++-----
 .../views/collections/CollectionStackingView.tsx   | 15 ++---
 .../CollectionStackingViewFieldColumn.tsx          | 20 +++----
 src/client/views/collections/CollectionSubView.tsx | 19 +++---
 .../views/collections/CollectionTimeView.tsx       |  7 ++-
 .../views/collections/CollectionTreeView.tsx       | 16 ++----
 src/client/views/collections/CollectionView.tsx    | 26 ++++-----
 src/client/views/collections/TabDocView.tsx        | 41 ++++++-------
 src/client/views/collections/TreeView.tsx          | 23 ++++----
 .../CollectionFreeFormInfoState.tsx                | 20 +++----
 .../CollectionFreeFormInfoUI.tsx                   | 33 ++++-------
 .../CollectionFreeFormLinkView.tsx                 | 15 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 13 +----
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |  1 -
 .../collections/collectionFreeForm/MarqueeView.tsx | 23 ++++----
 .../collectionGrid/CollectionGridView.tsx          |  7 ++-
 .../collectionLinear/CollectionLinearView.tsx      | 19 ++----
 .../CollectionMulticolumnView.tsx                  | 11 +++-
 .../CollectionMultirowView.tsx                     | 11 +++-
 .../collectionSchema/CollectionSchemaView.tsx      | 37 +++++-------
 .../collections/collectionSchema/SchemaRowBox.tsx  | 14 ++---
 .../collectionSchema/SchemaTableCell.tsx           | 67 +++++++++-------------
 src/client/views/linking/LinkMenu.tsx              |  9 +--
 src/client/views/linking/LinkMenuItem.tsx          | 26 ++++-----
 src/client/views/nodes/AudioBox.tsx                | 20 +++----
 .../views/nodes/CollectionFreeFormDocumentView.tsx | 29 ++++------
 src/client/views/nodes/ComparisonBox.tsx           | 20 ++-----
 src/client/views/nodes/DocumentContentsView.tsx    | 32 ++++-------
 src/client/views/nodes/DocumentIcon.tsx            | 26 ++++-----
 src/client/views/nodes/DocumentLinksButton.tsx     | 19 ++----
 src/client/views/nodes/DocumentView.tsx            | 36 ++++--------
 src/client/views/nodes/EquationBox.tsx             | 14 +----
 src/client/views/nodes/FontIconBox/FontIconBox.tsx | 14 ++---
 src/client/views/nodes/FunctionPlotBox.tsx         | 14 +----
 src/client/views/nodes/ImageBox.tsx                | 12 +---
 src/client/views/nodes/KeyValueBox.tsx             | 27 ++++-----
 src/client/views/nodes/KeyValuePair.tsx            | 29 ++++------
 src/client/views/nodes/LabelBox.tsx                | 14 ++---
 src/client/views/nodes/LinkAnchorBox.tsx           | 19 ++----
 src/client/views/nodes/LinkBox.tsx                 | 15 ++---
 src/client/views/nodes/LinkDocPreview.tsx          | 21 +++----
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |  5 +-
 src/client/views/nodes/MapBox/MapBox.tsx           | 22 +++----
 src/client/views/nodes/PDFBox.tsx                  | 12 +---
 src/client/views/nodes/VideoBox.tsx                | 13 +----
 src/client/views/nodes/WebBox.tsx                  | 11 +---
 src/client/views/nodes/audio/AudioWaveform.tsx     | 17 ++----
 .../views/nodes/formattedText/DashFieldView.tsx    | 18 +++---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 15 ++---
 .../views/nodes/formattedText/RichTextMenu.tsx     | 44 +++++---------
 src/client/views/nodes/trails/PresElementBox.tsx   | 17 ++----
 src/client/views/pdf/AnchorMenu.tsx                | 14 ++---
 src/client/views/pdf/PDFViewer.tsx                 | 22 +++----
 81 files changed, 578 insertions(+), 911 deletions(-)
 create mode 100644 src/client/views/ObservableReactComponent.tsx

(limited to 'src/client/views/collections/collectionGrid/CollectionGridView.tsx')

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8849f30dd..5239d22c0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,5 +13,7 @@
     "editor.defaultFormatter": "esbenp.prettier-vscode",
     "[typescriptreact]": {
         "editor.defaultFormatter": "esbenp.prettier-vscode"
-    }
+    },
+    "terminal.integrated.shellIntegration.showWelcome": false,
+    "terminal.integrated.shellIntegration.enabled": true
 }
diff --git a/src/Utils.ts b/src/Utils.ts
index d54760100..3a2bbf9a1 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -440,7 +440,12 @@ export namespace Utils {
         socket.on(message, (room: any) => handler(socket, room));
     }
 }
-export function copyProps(thing: { _prevProps: any; props: any; _props: any }) {
+export function copyProps(thing: { props: any; _props: any }, prevProps: any) {
+    Object.keys(prevProps).forEach(action(pkey =>
+        (prevProps)[pkey] !== (thing.props as any)[pkey] &&
+            ((thing._props as any)[pkey] = (thing.props as any)[pkey]))); // prettier-ignore
+}
+export function copyPropsFull(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
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7fcda75cc..4816f3317 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable, ObservableSet, observe, reaction } 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';
@@ -13,7 +13,6 @@ 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';
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 7cf0d62d0..4bd6647c0 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,4 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Doc, Opt } from '../../fields/Doc';
 import { DocViews } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
@@ -26,13 +26,13 @@ export class SelectionManager {
     }
 
     @action
-    public static SelectSchemaViewDoc(doc: Opt<Doc>, deselectAllFirst?: boolean) {
+    public static SelectSchemaViewDoc = (doc: Opt<Doc>, deselectAllFirst?: boolean) => {
         if (deselectAllFirst) this.DeselectAll();
         this.Instance.SelectedSchemaDocument = doc;
-    }
+    };
 
     @action
-    public static SelectView(docView: DocumentView, extendSelection: boolean): void {
+    public static SelectView = (docView: DocumentView | undefined, extendSelection: boolean): void => {
         if (!docView) this.DeselectAll();
         else if (!docView.SELECTED) {
             if (!extendSelection) this.DeselectAll();
@@ -40,29 +40,29 @@ export class SelectionManager {
             docView.SELECTED = true;
             docView._props.whenChildContentsActiveChanged(true);
         }
-    }
+    };
 
     @action
-    public static DeselectView(docView?: DocumentView): void {
+    public static DeselectView = (docView?: DocumentView): void => {
         if (docView && this.Instance.SelectedViews.includes(docView)) {
             docView.SELECTED = false;
             this.Instance.SelectedViews.splice(this.Instance.SelectedViews.indexOf(docView), 1);
             docView._props.whenChildContentsActiveChanged(false);
         }
-    }
-    @action
-    public static DeselectAll(except?: Doc): void {
+    };
+
+    public static DeselectAll = (except?: Doc): void => {
         const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
         LinkManager.currentLink = undefined;
         LinkManager.currentLinkAnchor = undefined;
-        this.Instance.SelectedSchemaDocument = undefined;
+        runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
         this.Instance.SelectedViews.forEach(dv => {
             dv.SELECTED = 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 get Views() { return this.Instance.SelectedViews; } // prettier-ignore
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index f89624941..4c82b10fd 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -1,32 +1,25 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import * as React from 'react';
 import { SettingsManager } from '../util/SettingsManager';
-import { copyProps } from '../../Utils';
 import './AntimodeMenu.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
 export interface AntimodeMenuProps {}
 
 /**
  * This is an abstract class that serves as the base for a PDF-style or Marquee-style
  * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
  */
-export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends React.Component<T, {}> {
+export abstract class AntimodeMenu<T extends AntimodeMenuProps> extends ObservableReactComponent<T> {
     protected _offsetY: number = 0;
     protected _offsetX: number = 0;
     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>) {
+    constructor(props: any) {
         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/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 118a2c5f5..e55bf24a1 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -6,9 +6,10 @@ import { StrCast } from '../../fields/Types';
 import { SettingsManager } from '../util/SettingsManager';
 import './ContextMenu.scss';
 import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 @observer
-export class ContextMenu extends React.Component {
+export class ContextMenu extends ObservableReactComponent<{}> {
     static Instance: ContextMenu;
 
     private _ignoreUp = false;
@@ -36,8 +37,8 @@ export class ContextMenu extends React.Component {
 
     constructor(props: any) {
         super(props);
-        makeObservable(this);
         ContextMenu.Instance = this;
+        makeObservable(this);
     }
 
     public setIgnoreEvents(ignore: boolean) {
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 59b223c14..3c9d821a9 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -5,7 +5,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { UndoManager } from '../util/UndoManager';
 import { SettingsManager } from '../util/SettingsManager';
-import { copyProps } from '../../Utils';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 export interface OriginalMenuProps {
     description: string;
@@ -27,24 +27,17 @@ export interface SubmenuProps {
 export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
 
 @observer
-export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> {
+export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps & { selected?: boolean }> {
     @observable private _items: Array<ContextMenuProps> = [];
     @observable private overItem = false;
 
-    _prevProps: ContextMenuProps & { selected?: boolean };
-    @observable _props: ContextMenuProps & { selected?: boolean };
-    constructor(props: ContextMenuProps & { selected?: boolean }) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     componentDidMount() {
-        runInAction(() => 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/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 35c0d617b..85cee83d4 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -27,6 +27,7 @@ import './DashboardView.scss';
 import { Colors } from './global/globalEnums';
 import { MainViewModal } from './MainViewModal';
 import { ButtonType } from './nodes/FontIconBox/FontIconBox';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 enum DashboardGroup {
     MyDashboards,
@@ -36,9 +37,8 @@ enum DashboardGroup {
 // DashboardView is the view with the dashboard previews, rendered when the app first loads
 
 @observer
-export class DashboardView extends React.Component {
+export class DashboardView extends ObservableReactComponent<{}> {
     public static _urlState: HistoryUtil.DocUrl;
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 2ce0c085a..235b0dc68 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,17 +1,17 @@
-import { action, computed, makeObservable, observable, runInAction, untracked } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
+import * as React from 'react';
+import { returnFalse } from '../../Utils';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
-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 { DocumentView } from './nodes/DocumentView';
-import * as React from 'react';
+import { DocUtils } from '../documents/Documents';
 import { DocumentManager } from '../util/DocumentManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { DocumentView } from './nodes/DocumentView';
 
 ///  DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
 export interface DocComponentProps {
@@ -20,18 +20,14 @@ export interface DocComponentProps {
     LayoutTemplateString?: string;
 }
 export function DocComponent<P extends DocComponentProps>() {
-    class Component extends React.Component<React.PropsWithChildren<P>> {
-        @observable _props!: React.PropsWithChildren<P>;
-        constructor(props: React.PropsWithChildren<P>) {
+    class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        constructor(props: any) {
             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() {
+        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
@@ -57,24 +53,18 @@ interface ViewBoxBaseProps {
     renderDepth: number;
 }
 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);
-        }
+    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;
         }
         // 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
-        get layoutDoc() {
+        @computed 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
-        get dataDoc() {
+        @computed get dataDoc() {
             return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData];
         }
         // key where data is stored
@@ -99,13 +89,12 @@ export interface ViewBoxAnnotatableProps {
     isAnnotationOverlay?: boolean;
 }
 export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() {
-    class Component extends React.Component<React.PropsWithChildren<P>> {
-        @observable _props: React.PropsWithChildren<P>;
-        constructor(props: React.PropsWithChildren<P>) {
+    class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+        constructor(props: any) {
             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
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index a8ad4150e..50ca3af06 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -28,6 +28,7 @@ import { GoogleRef } from './nodes/formattedText/FormattedTextBox';
 import { PinProps } from './nodes/trails';
 import { TemplateMenu } from './TemplateMenu';
 import * as React from 'react';
+import { ObservableReactComponent } from './ObservableReactComponent';
 // import * as higflyout from '@hig/flyout';
 // export const { anchorPoints } = higflyout;
 // export const Flyout = higflyout.default;
@@ -42,12 +43,11 @@ enum UtilityButtonState {
 }
 
 @observer
-export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[]; stack?: any }, {}> {
+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 private pushIcon: IconProp = 'arrow-alt-circle-up';
     @observable private pullIcon: IconProp = 'arrow-alt-circle-down';
     @observable private pullColor: string = 'white';
@@ -60,12 +60,10 @@ 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;
+        DocumentButtonBar.Instance = this;
         makeObservable(this);
-        runInAction(() => (DocumentButtonBar.Instance = this));
     }
 
     public startPullOutcome = action((success: boolean) => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 8bcc1cb56..7003485d2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -34,6 +34,7 @@ import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import * as React from 'react';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -42,7 +43,7 @@ interface DocumentDecorationsProps {
     boundsTop: number;
 }
 @observer
-export class DocumentDecorations extends React.Component<DocumentDecorationsProps> {
+export class DocumentDecorations extends ObservableReactComponent<DocumentDecorationsProps> {
     static Instance: DocumentDecorations;
     private _resizeHdlId = '';
     private _keyinput = React.createRef<HTMLInputElement>();
@@ -66,11 +67,10 @@ export class DocumentDecorations extends React.Component<DocumentDecorationsProp
     @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!)
 
-    @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 => {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 3db77be71..836a184eb 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,13 +1,13 @@
-import * as React from 'react';
 import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from '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';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { OverlayView } from './OverlayView';
 
 export interface EditableProps {
     /**
@@ -60,18 +60,15 @@ export interface EditableProps {
  * of the content, and set the value based on the entered string.
  */
 @observer
-export class EditableView extends React.Component<EditableProps> {
+export class EditableView extends ObservableReactComponent<EditableProps> {
     private _ref = React.createRef<HTMLDivElement>();
     private _inputref: HTMLInputElement | HTMLTextAreaElement | null = null;
     _overlayDisposer?: () => void;
     _editingDisposer?: IReactionDisposer;
     @observable _editing: boolean = false;
 
-    _prevProps: EditableProps;
-    @observable _props: EditableProps;
     constructor(props: EditableProps) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
         this._editing = this._props.editing ? true : false;
     }
@@ -96,8 +93,7 @@ export class EditableView extends React.Component<EditableProps> {
         );
     }
 
-    componentDidUpdate() {
-        copyProps(this);
+    componentDidUpdate(prevProps: Readonly<EditableProps>) {
         if (this._editing && this._props.editing === false) {
             this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false);
         } else if (this._props.editing !== undefined) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4f96b9234..f65675792 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,20 +8,19 @@ import { observer } from 'mobx-react';
 import 'normalize.css';
 import * as React from 'react';
 import '../../../node_modules/browndash-components/dist/styles/global.min.css';
+import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
 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';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
-import { Docs } from '../documents/Documents';
+import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
 import { CaptureManager } from '../util/CaptureManager';
 import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
 import { GroupManager } from '../util/GroupManager';
 import { HistoryUtil } from '../util/History';
 import { Hypothesis } from '../util/HypothesisUtils';
-import { ReportManager } from '../util/reportManager/ReportManager';
 import { RTFMarkup } from '../util/RTFMarkup';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SelectionManager } from '../util/SelectionManager';
@@ -30,13 +29,7 @@ import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
-import { TimelineMenu } from './animationtimeline/TimelineMenu';
-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 { ReportManager } from '../util/reportManager/ReportManager';
 import { ComponentDecorations } from './ComponentDecorations';
 import { ContextMenu } from './ContextMenu';
 import { DashboardView } from './DashboardView';
@@ -45,15 +38,24 @@ import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
 import { KeyManager } from './GlobalKeyHandler';
 import { LightboxView } from './LightboxView';
-import { LinkMenu } from './linking/LinkMenu';
 import './MainView.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { OverlayView } from './OverlayView';
+import { PreviewCursor } from './PreviewCursor';
+import { PropertiesView } from './PropertiesView';
+import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
+import { TimelineMenu } from './animationtimeline/TimelineMenu';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionMenu } from './collections/CollectionMenu';
+import { TabDocView } from './collections/TabDocView';
+import './collections/TreeView.scss';
+import { CollectionFreeFormLinksView } from './collections/collectionFreeForm';
+import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import { CollectionLinearView } from './collections/collectionLinear';
+import { LinkMenu } from './linking/LinkMenu';
 import { AudioBox } from './nodes/AudioBox';
 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';
-import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
-import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 import { ImageBox } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
@@ -61,20 +63,19 @@ import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
 import { MapBox } from './nodes/MapBox/MapBox';
 import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
+import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 import { PresBox } from './nodes/trails';
-import { OverlayView } from './OverlayView';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
-import { PreviewCursor } from './PreviewCursor';
-import { PropertiesView } from './PropertiesView';
-import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
-import { CollectionFreeFormLinksView } from './collections/collectionFreeForm';
 const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
-export class MainView extends React.Component {
+export class MainView extends ObservableReactComponent<{}> {
     public static Instance: MainView;
     public static Live: boolean = false;
     private _docBtnRef = React.createRef<HTMLDivElement>();
@@ -1064,7 +1065,7 @@ export class MainView extends React.Component {
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
                 <CollectionFreeFormLinksView />
                 <OverlayView />
-                {this.mapBoxHack}
+                {/* {this.mapBoxHack} */}
                 <GPTPopup key="gptpopup" />
                 <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
                 {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 3f0db4258..2e27d1f70 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -15,6 +15,7 @@ import './MarqueeAnnotator.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { AnchorMenu } from './pdf/AnchorMenu';
+import { ObservableReactComponent } from './ObservableReactComponent';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export interface MarqueeAnnotatorProps {
@@ -38,23 +39,18 @@ export interface MarqueeAnnotatorProps {
     highlightDragSrcColor?: string;
 }
 @observer
-export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
+export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorProps> {
     private _start: { x: number; y: number } = { x: 0, y: 0 };
-    @observable private _width: number = 0;
-    @observable private _height: number = 0;
-    @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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidMount() {
-        copyProps(this);
-    }
+
+    @observable private _width: number = 0;
+    @observable private _height: number = 0;
+    @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[]>) {
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
new file mode 100644
index 000000000..2d8dc9af9
--- /dev/null
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -0,0 +1,21 @@
+import { makeObservable, observable } from 'mobx';
+import * as React from 'react';
+import { copyProps } from '../../Utils';
+import './AntimodeMenu.scss';
+export interface AntimodeMenuProps {}
+
+/**
+ * This is an abstract class that serves as the base for a PDF-style or Marquee-style
+ * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
+ */
+export abstract class ObservableReactComponent<T> extends React.Component<T, {}> {
+    @observable _props: React.PropsWithChildren<T>;
+    constructor(props: React.PropsWithChildren<T>) {
+        super(props);
+        this._props = props;
+        makeObservable(this);
+    }
+    componentDidUpdate(prevProps: Readonly<T>): void {
+        copyProps(this, prevProps);
+    }
+}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 673432a60..915c3c18f 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,9 +1,9 @@
-import { action, computed, makeObservable, observable, toJS } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
-import { Utils, copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc } from '../../fields/Doc';
 import { Height, Width } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
@@ -12,6 +12,7 @@ import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from '../util/DragManager';
 import { Transform } from '../util/Transform';
 import { LightboxView } from './LightboxView';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import './OverlayView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
@@ -34,19 +35,14 @@ export interface OverlayWindowProps {
 }
 
 @observer
-export class OverlayWindow extends React.Component<OverlayWindowProps> {
+export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps> {
     @observable x: number = 0;
     @observable y: number = 0;
     @observable width: number = 0;
     @observable height: number = 0;
-
-    _prevProps: OverlayWindowProps;
-    @observable _props: OverlayWindowProps;
     constructor(props: OverlayWindowProps) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
-
         const opts = props.overlayOptions;
         this.x = opts.x;
         this.y = opts.y;
@@ -54,10 +50,6 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
         this.height = opts.height || 200;
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     onPointerDown = (_: React.PointerEvent) => {
         document.removeEventListener('pointermove', this.onPointerMove);
         document.removeEventListener('pointerup', this.onPointerUp);
@@ -115,7 +107,7 @@ export class OverlayWindow extends React.Component<OverlayWindowProps> {
 }
 
 @observer
-export class OverlayView extends React.Component {
+export class OverlayView extends ObservableReactComponent<{}> {
     public static Instance: OverlayView;
     @observable.shallow _elements: JSX.Element[] = [];
 
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 6e1a2cfb1..5ec9a7d46 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,17 +1,18 @@
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Doc, Opt } from '../../fields/Doc';
 import { lightOrDark, returnFalse } from '../../Utils';
-import { Docs, DocumentOptions, DocUtils } from '../documents/Documents';
+import { Doc, Opt } from '../../fields/Doc';
+import { DocUtils, Docs, DocumentOptions } from '../documents/Documents';
 import { ImageUtils } from '../util/Import & Export/ImageUtils';
 import { Transform } from '../util/Transform';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { UndoManager, undoBatch } from '../util/UndoManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import './PreviewCursor.scss';
+import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 
 @observer
-export class PreviewCursor extends React.Component<{}> {
+export class PreviewCursor extends ObservableReactComponent<{}> {
     static _instance: PreviewCursor;
     public static get Instance() {
         return PreviewCursor._instance;
@@ -28,8 +29,8 @@ export class PreviewCursor extends React.Component<{}> {
     public Doc: Opt<Doc>;
     constructor(props: any) {
         super(props);
-        PreviewCursor._instance = this;
         makeObservable(this);
+        PreviewCursor._instance = this;
         this._clickPoint = observable([0, 0]);
         document.addEventListener('keydown', this.onKeyPress);
         document.addEventListener('paste', this.paste, true);
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 5bde9d3c4..a710e7816 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,14 +1,14 @@
-import { computed, makeObservable, observable } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { Cast, StrCast } from '../../fields/Types';
 import { DocFocusOrOpen } from '../util/DocumentManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import './PropertiesDocContextSelector.scss';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
-import './PropertiesDocContextSelector.scss';
-import { copyProps } from '../../Utils';
 
 type PropertiesDocContextSelectorProps = {
     DocView?: DocumentView;
@@ -18,17 +18,12 @@ type PropertiesDocContextSelectorProps = {
 };
 
 @observer
-export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
-    _prevProps: PropertiesDocContextSelectorProps;
-    @observable _props: PropertiesDocContextSelectorProps;
-    constructor(props: PropertiesDocContextSelectorProps) {
+export class PropertiesDocContextSelector extends ObservableReactComponent<PropertiesDocContextSelectorProps> {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
+
     @computed get _docs() {
         if (!this._props.DocView) return [];
         const target = this._props.DocView._props.Document;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 442d09739..c857c2d6b 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1,14 +1,15 @@
-import * as React from 'react';
 import { IconLookup } from '@fortawesome/fontawesome-svg-core';
 import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-solid-svg-icons';
 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, 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 { ColorResult, SketchPicker } from 'react-color';
 import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs"
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc, DocListCast, Field, FieldResult, HierarchyMapping, NumListCast, Opt, ReverseHierarchyMap, StrListCast } from '../../fields/Doc';
 import { AclAdmin, DocAcl, DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
@@ -16,8 +17,7 @@ import { InkField } from '../../fields/InkField';
 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 { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { DocumentManager } from '../util/DocumentManager';
 import { GroupManager } from '../util/GroupManager';
@@ -26,19 +26,20 @@ import { SelectionManager } from '../util/SelectionManager';
 import { SettingsManager } from '../util/SettingsManager';
 import { SharingManager } from '../util/SharingManager';
 import { Transform } from '../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../util/UndoManager';
 import { EditableView } from './EditableView';
 import { FilterPanel } from './FilterPanel';
 import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { KeyValueBox } from './nodes/KeyValueBox';
-import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import { PropertiesButtons } from './PropertiesButtons';
 import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector';
 import { PropertiesDocContextSelector } from './PropertiesDocContextSelector';
 import { PropertiesSection } from './PropertiesSection';
 import './PropertiesView.scss';
 import { DefaultStyleProvider } from './StyleProvider';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface PropertiesViewProps {
@@ -49,15 +50,12 @@ interface PropertiesViewProps {
 }
 
 @observer
-export class PropertiesView extends React.Component<PropertiesViewProps> {
+export class PropertiesView extends ObservableReactComponent<PropertiesViewProps> {
     private _widthUndo?: UndoManager.Batch;
 
     public static Instance: PropertiesView | undefined;
-    _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;
     }
@@ -120,10 +118,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
         );
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     componentWillUnmount() {
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 8251d20dc..5f20bc745 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, makeObservable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DocumentManager } from '../util/DocumentManager';
@@ -7,29 +7,23 @@ import { CompileScript, Transformer, ts } from '../util/Scripting';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
 import { SettingsManager } from '../util/SettingsManager';
 import { undoable } from '../util/UndoManager';
-import { DocumentIconContainer } from './nodes/DocumentIcon';
+import { ObservableReactComponent } from './ObservableReactComponent';
 import { OverlayView } from './OverlayView';
 import './ScriptingRepl.scss';
-import { copyProps } from '../../Utils';
+import { DocumentIconContainer } from './nodes/DocumentIcon';
 
 interface ReplProps {
     scrollToBottom: () => void;
     value: { [key: string]: any };
     name?: string;
 }
-export class ScriptingObjectDisplay extends React.Component<ReplProps> {
+export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps> {
     @observable collapsed = true;
 
-    _prevProps: ReplProps;
-    @observable _props: ReplProps;
-    constructor(props: ReplProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate(): void {
-        copyProps(this);
-    }
 
     @action
     toggle = () => {
@@ -84,17 +78,12 @@ interface replValueProps {
     name?: string;
 }
 @observer
-export class ScriptingValueDisplay extends React.Component<replValueProps> {
-    _prevProps: replValueProps;
-    @observable _props: replValueProps;
-    constructor(props: replValueProps) {
+export class ScriptingValueDisplay extends ObservableReactComponent<replValueProps> {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
+
     render() {
         const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
         if (typeof val === 'object') {
@@ -126,7 +115,7 @@ export class ScriptingValueDisplay extends React.Component<replValueProps> {
 }
 
 @observer
-export class ScriptingRepl extends React.Component {
+export class ScriptingRepl extends ObservableReactComponent<{}> {
     constructor(props: any) {
         super(props);
         makeObservable(this);
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 19473de2b..0f4a4260c 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,22 +1,23 @@
-import { computed, makeObservable, observable } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnAll, returnFalse, returnOne, returnZero } from '../../Utils';
 import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { DocCast, NumCast, StrCast } from '../../fields/Types';
-import { copyProps, emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
+import { DocUtils, Docs } from '../documents/Documents';
 import { LinkManager } from '../util/LinkManager';
 import { SearchUtil } from '../util/SearchUtil';
 import { Transform } from '../util/Transform';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import './SidebarAnnos.scss';
+import { StyleProp } from './StyleProvider';
 import { CollectionStackingView } from './collections/CollectionStackingView';
 import { FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
-import './SidebarAnnos.scss';
-import { StyleProp } from './StyleProvider';
-import * as React from 'react';
 
 interface ExtraProps {
     fieldKey: string;
@@ -34,18 +35,12 @@ interface ExtraProps {
     moveDocument: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string) => boolean;
 }
 @observer
-export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
-    _prevProps: FieldViewProps & ExtraProps;
-    @observable _props: FieldViewProps & ExtraProps;
-    constructor(props: FieldViewProps & ExtraProps) {
+export class SidebarAnnos extends ObservableReactComponent<FieldViewProps & ExtraProps> {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
-        // this._props.dataDoc[this.sidebarKey] = new List<Doc>();  // bcz: can't do this here.  it blows away existing things and isn't a robust solution for making sure the field exists -- instead this should happen when the document is created and/or shared
-    }
-    componentDidUpdate() {
-        copyProps(this);
     }
+
     _stackRef = React.createRef<CollectionStackingView>();
     @computed get allMetadata() {
         const keys = new Map<string, FieldResult<Field>>();
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index f03e38850..a11c53d4d 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils, returnFalse, returnZero } from '../../../Utils';
@@ -19,6 +19,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     @computed get scrollSpeed() {
         return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
     }
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
 
     private _dropDisposer?: DragManager.DragDropDisposer;
 
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 299a4d5d3..5d09f14ef 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { StopEvent, emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
@@ -16,6 +16,11 @@ import { CollectionSubView } from './CollectionSubView';
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentWillUnmount() {
         this._dropDisposer?.();
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4ddf9e69b..0d3ff8bba 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,5 +1,6 @@
-import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
+import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import * as GoldenLayout from '../../../client/goldenLayout';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -10,7 +11,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, copyProps, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -19,6 +20,7 @@ import { DragManager } from '../../util/DragManager';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
 import { LightboxView } from '../LightboxView';
@@ -30,8 +32,6 @@ import './CollectionDockingView.scss';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
-import * as React from 'react';
-import { SettingsManager } from '../../util/SettingsManager';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -63,11 +63,8 @@ export class CollectionDockingView extends CollectionSubView() {
     private _goldenLayout: any = null;
     static _highlightStyleSheet: any = addStyleSheet();
 
-    _prevProps: SubCollectionViewProps;
-    @override _props: SubCollectionViewProps;
     constructor(props: SubCollectionViewProps) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
         if (this._props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
         //Why is this here?
@@ -77,10 +74,6 @@ 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.
      *
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 0905aa078..5dba9e72a 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { emptyFunction, numberRange, returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { CompileScript } from '../../util/Scripting';
@@ -14,6 +14,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionStackingView } from './CollectionStackingView';
 import './CollectionStackingView.scss';
@@ -41,19 +42,18 @@ interface CMVFieldRowProps {
 }
 
 @observer
-export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> {
+export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVFieldRowProps> {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private _background = 'inherit';
     @observable private _createEmbeddingSelected: boolean = false;
     @observable private heading: string = '';
     @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));
     }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 08fcd544e..3ade2ab56 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -9,7 +9,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { RichTextField } from '../../../fields/RichTextField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-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 { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 702775307..f9d258490 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
@@ -44,6 +44,12 @@ export class CollectionNoteTakingView extends CollectionSubView() {
     public DividerWidth = 16;
     @observable docsDraggedRowCol: number[] = [];
     @observable _scroll = 0;
+
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @computed get chromeHidden() {
         return BoolCast(this.layoutDoc.chromeHidden) || this.props.onBrowseClick?.() ? true : false;
     }
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index a0f8b9c89..170b1f1ec 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { ScriptField } from '../../../fields/ScriptField';
@@ -19,6 +19,11 @@ export class CollectionPileView extends CollectionSubView() {
     _originalChrome: any = '';
     _disposers: { [name: string]: IReactionDisposer } = {};
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     componentDidMount() {
         if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) {
             this.Document._freeform_pileEngine = computePassLayout.name;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1b14b31ac..d37a942d0 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
@@ -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 { copyProps, emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { 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';
@@ -26,6 +26,7 @@ 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';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import './CollectionStackedTimeline.scss';
 
 export type CollectionStackedTimelineProps = {
@@ -56,17 +57,10 @@ export enum TrimScope {
 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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     static LabelScript: ScriptField;
     static LabelPlayScript: ScriptField;
@@ -697,21 +691,15 @@ interface StackedTimelineAnchorProps {
 }
 
 @observer
-class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
+class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnchorProps> {
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    _prevProps: React.PropsWithChildren<StackedTimelineAnchorProps>;
-    @observable _props: React.PropsWithChildren<StackedTimelineAnchorProps>;
     constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
         this._lastTimecode = this._props.currentTimecode();
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     // updates marker document title to reflect correct timecodes
     computeTitle = () => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d6105c184..16adf4b96 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,8 +1,8 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as CSS from 'csstype';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -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 { copyProps, emptyFunction, 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';
@@ -31,7 +31,7 @@ import { StyleProp } from '../StyleProvider';
 import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
 import './CollectionStackingView.scss';
 import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
-import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionStackingViewProps = {
@@ -115,22 +115,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         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?
             // here we're making an empty list of column headers (again, what Mehek showed us)
             this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>();
         }
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     // TODO: plj - these are the children
     children = (docs: Doc[]) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 7ea146479..2302bfbc3 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { RichTextField } from '../../../fields/RichTextField';
 import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
+import { BoolCast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from '../../../Utils';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
@@ -19,9 +19,9 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
-import './CollectionStackingView.scss';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { Id } from '../../../fields/FieldSymbols';
+import './CollectionStackingView.scss';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 
 // So this is how we are storing a column
 interface CSVFieldColumnProps {
@@ -49,7 +49,7 @@ interface CSVFieldColumnProps {
 }
 
 @observer
-export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldColumnProps> {
+export class CollectionStackingViewFieldColumn extends ObservableReactComponent<CSVFieldColumnProps> {
     private dropDisposer?: DragManager.DragDropDisposer;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -58,10 +58,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
     @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';
@@ -80,10 +78,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         }
     };
 
-    componentDidUpdate() {
-        // untracked(() => (this._props = this.props));
-    }
-
     @action
     componentDidMount() {
         this._disposers.collapser = reaction(
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 79b15fdb1..0131af6f2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,5 +1,7 @@
-import { action, computed, makeObservable, observable, override, runInAction, untracked } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
+import * as React from 'react';
 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 } from '../../../fields/DocSymbols';
@@ -10,21 +12,19 @@ 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';
-import { returnFalse, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
-import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../DocComponent';
-import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { LoadingBox } from '../nodes/LoadingBox';
+import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { CollectionView, CollectionViewProps } from './CollectionView';
-import * as React from 'react';
 
 export interface SubCollectionViewProps extends CollectionViewProps {
     isAnyChildContentActive: () => boolean;
@@ -35,16 +35,11 @@ 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
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index e65f24702..c440a7e3f 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,5 +1,5 @@
 import { toUpper } from 'lodash';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
@@ -32,6 +32,11 @@ export class CollectionTimeView extends CollectionSubView() {
     @observable _viewDefDivClick: Opt<ScriptField>;
     @observable _focusPivotField: Opt<string>;
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     async componentDidMount() {
         this.props.setContentView?.(this);
         runInAction(() => {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 658d60523..7f8d42088 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,5 +1,6 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, override, 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, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -7,7 +8,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 { copyProps, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
+import { 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,10 +24,9 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
 import { CollectionFreeFormView } from './collectionFreeForm';
-import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { CollectionSubView } from './CollectionSubView';
 import './CollectionTreeView.scss';
 import { TreeView } from './TreeView';
-import * as React from 'react';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 export type collectionTreeViewProps = {
@@ -59,16 +59,10 @@ 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.
 
-    _prevProps: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
-    @override _props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>;
-    constructor(props: React.PropsWithChildren<SubCollectionViewProps & Partial<collectionTreeViewProps>>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     get dataDoc() {
         return this._props.TemplateDataDocument || this.Document;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e84c12e02..f1ef52e60 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,14 +1,14 @@
-import { IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { returnEmptyString } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { ObjectField } from '../../../fields/ObjectField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { copyProps, returnEmptyString } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
+import { DocUtils } from '../../documents/Documents';
 import { dropActionType } from '../../util/DragManager';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { ContextMenu } from '../ContextMenu';
@@ -19,19 +19,19 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionLinearView } from './collectionLinear';
-import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
-import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionNoteTakingView } from './CollectionNoteTakingView';
 import { CollectionPileView } from './CollectionPileView';
-import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
 import { CollectionStackingView } from './CollectionStackingView';
 import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from './CollectionTreeView';
 import './CollectionView.scss';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
+import { CollectionLinearView } from './collectionLinear';
+import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
+import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
+import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
 const path = require('path');
 
 interface CollectionViewProps_ extends FieldViewProps {
@@ -78,16 +78,10 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     private reactionDisposer: IReactionDisposer | 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);
+        this._annotationKeySuffix = returnEmptyString;
     }
 
     componentDidMount() {
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 8bc0f62dc..2eb13fd2f 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -2,40 +2,41 @@ 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, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
+import { DashColor, Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { FieldId } from '../../../fields/RefField';
+import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { copyProps, DashColor, emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoable, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoable } from '../../util/UndoManager';
 import { DashboardView } from '../DashboardView';
-import { Colors } from '../global/globalEnums';
 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 { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { KeyValueBox } from '../nodes/KeyValueBox';
+import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
-import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
 import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
-import * as React from 'react';
-import { Docs } from '../../documents/Documents';
-import { ComputedField } from '../../../fields/ScriptField';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
@@ -44,16 +45,13 @@ interface TabDocViewProps {
     glContainer: any;
 }
 @observer
-export class TabDocView extends React.Component<TabDocViewProps> {
+export class TabDocView extends ObservableReactComponent<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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
@@ -341,9 +339,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         //     { fireImmediately: true });
         runInAction(() => TabDocView._allTabs.add(this));
     }
-    componentDidUpdate() {
+    componentDidUpdate(prevProps: Readonly<TabDocViewProps>) {
         this._view && DocumentManager.Instance.AddView(this._view);
-        copyProps(this);
     }
 
     componentWillUnmount() {
@@ -521,14 +518,14 @@ interface TabMiniThumbProps {
     miniTop: () => number;
     miniLeft: () => number;
 }
-@observer
+
 class TabMiniThumb extends React.Component<TabMiniThumbProps> {
     render() {
         return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
     }
 }
 @observer
-export class TabMinimapView extends React.Component<TabMinimapViewProps> {
+export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps> {
     static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc) {
             switch (property.split(':')[0]) {
@@ -556,12 +553,6 @@ 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 bounds = compView?.freeformData?.(true)?.bounds;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 224d74882..52ecdfbec 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,8 +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, 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 { Utils, emptyFunction, lightOrDark, return18, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick } from '../../../Utils';
 import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -12,9 +14,8 @@ 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 { 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 { DocUtils, Docs } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
@@ -22,19 +23,19 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SettingsManager } from '../../util/SettingsManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoable, undoBatch, UndoManager } from '../../util/UndoManager';
+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 { FieldViewProps } from '../nodes/FieldView';
+import { KeyValueBox } from '../nodes/KeyValueBox';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-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 * as React from 'react';
 const { default: { TREE_BULLET_WIDTH } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export interface TreeViewProps {
@@ -86,7 +87,7 @@ const treeBulletWidth = function () {
  * treeView_ExpandedView : name of field whose contents are being displayed as the document's subtree
  */
 @observer
-export class TreeView extends React.Component<TreeViewProps> {
+export class TreeView extends ObservableReactComponent<TreeViewProps> {
     static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>;
     static _openTitleScript: Opt<ScriptField | undefined>;
     static _openLevelScript: Opt<ScriptField | undefined>;
@@ -254,11 +255,8 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     static GetRunningChildren = new Map<Doc, any>();
     static ToggleChildrenRun = new Map<Doc, () => void>();
-    _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' });
@@ -297,8 +295,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         this._props.hierarchyIndex !== undefined && this._props.RemFromMap?.(this.Document, this._props.hierarchyIndex);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
+    componentDidUpdate(prevProps: Readonly<TreeViewProps>) {
         this._disposers.opening = reaction(
             () => this.treeViewOpen,
             open => !open && (this._renderCount = 20)
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
index dd7e12e41..0d5fcdaeb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -1,8 +1,9 @@
-import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { copyProps } from '../../../../Utils';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormView.scss';
+import { copyProps } from '../../../../Utils';
 
 /**
  * An Fsa Arc.  The first array element is a test condition function that will be observed.
@@ -47,14 +48,11 @@ export interface CollectionFreeFormInfoStateProps {
 }
 
 @observer
-export class CollectionFreeFormInfoState extends React.Component<CollectionFreeFormInfoStateProps> {
+export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> {
     _disposers: IReactionDisposer[] = [];
 
-    _prevProps: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
-    @observable _props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>;
-    constructor(props: React.PropsWithChildren<CollectionFreeFormInfoStateProps>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
@@ -62,7 +60,7 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
         return this._props.infoState;
     }
     get Arcs() {
-        return Object.keys(this.State).map(key => this.State[key]);
+        return Object.keys(this.State ?? []).map(key => this.State?.[key]);
     }
 
     clearState = () => this._disposers.map(disposer => disposer());
@@ -84,8 +82,8 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
     componentDidMount(): void {
         this.initState();
     }
-    componentDidUpdate() {
-        copyProps(this);
+    componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) {
+        copyProps(this, prevProps);
         this.clearState();
         this.initState();
     }
@@ -93,6 +91,6 @@ export class CollectionFreeFormInfoState extends React.Component<CollectionFreeF
         this.clearState();
     }
     render() {
-        return <div className="infoUI">{this.State[StateMessage]}</div>;
+        return <div className="infoUI">{this.State?.[StateMessage]}</div>;
     }
 }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index f0a052c1d..4d3752c02 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -1,21 +1,13 @@
-import { IReactionDisposer, computed, observable, reaction, action, runInAction, makeObservable } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, runInAction } 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 * as React from 'react';
-import { CollectionFreeFormView } from './CollectionFreeFormView';
-import { NumCast } from '../../../../fields/Types';
+import { Doc } from '../../../../fields/Doc';
 import { LinkManager } from '../../../util/LinkManager';
-import { InkTool } from '../../../../fields/InkField';
-import { LinkDocPreview } from '../../nodes/LinkDocPreview';
-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';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DocButtonState } from '../../nodes/DocumentLinksButton';
+import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState';
+import { CollectionFreeFormView } from './CollectionFreeFormView';
+import './CollectionFreeFormView.scss';
 
 export interface CollectionFreeFormInfoUIProps {
     Document: Doc;
@@ -23,7 +15,7 @@ export interface CollectionFreeFormInfoUIProps {
 }
 
 @observer
-export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeFormInfoUIProps> {
+export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
     private _disposers: { [name: string]: IReactionDisposer } = {};
 
     @observable _currState: infoState | undefined = undefined;
@@ -33,17 +25,12 @@ export class CollectionFreeFormInfoUI extends React.Component<CollectionFreeForm
     set currState(val) {
         this._currState = val;
     }
-    _prevProps: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
-    @observable _props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>;
-    constructor(props: React.PropsWithChildren<CollectionFreeFormInfoUIProps>) {
+
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = this.props;
         makeObservable(this);
         this.currState = this.setupStates();
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     setCurrState = (state: infoState) => {
         if (state) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index be3d2439c..6337c8d34 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,19 +1,20 @@
 import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc, Field } from '../../../../fields/Doc';
 import { Brushed, DocCss } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { Cast, NumCast, StrCast } from '../../../../fields/Types';
-import { copyProps, emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../../nodes/DocumentView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import './CollectionFreeFormLinkView.scss';
-import * as React from 'react';
 
 export interface CollectionFreeFormLinkViewProps {
     A: DocumentView;
@@ -24,22 +25,16 @@ export interface CollectionFreeFormLinkViewProps {
 // props.screentolocatransform
 
 @observer
-export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
+export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
     @observable _opacity: number = 0;
     @observable _start = 0;
     _anchorDisposer: IReactionDisposer | undefined;
     _timeout: NodeJS.Timeout | undefined;
-    _prevProps: CollectionFreeFormLinkViewProps;
-    @observable _props: CollectionFreeFormLinkViewProps;
-    constructor(props: CollectionFreeFormLinkViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
     componentWillUnmount() {
         this._anchorDisposer?.();
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aa40fb809..1f4688729 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, makeObservable, observable, override, reaction, runInAction, toJS } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, 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, copyProps, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, 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';
@@ -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, SubCollectionViewProps } from '../CollectionSubView';
+import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
@@ -72,17 +72,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         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;
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 825bd5f19..7aa68b0d9 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -20,7 +20,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     public isShown = () => this._opacity > 0;
     constructor(props: Readonly<{}>) {
         super(props);
-
         MarqueeOptionsMenu.Instance = this;
     }
 
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 10beb120a..2c65726b2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,5 +1,7 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Utils, intersectRect, lightOrDark, returnFalse } from '../../../../Utils';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -7,25 +9,24 @@ import { InkData, InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField, nullAudio } from '../../../../fields/URLField';
+import { ImageField } from '../../../../fields/URLField';
 import { GetEffectiveAcl } from '../../../../fields/util';
-import { intersectRect, lightOrDark, returnFalse, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
-import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocUtils, Docs, DocumentOptions } from '../../../documents/Documents';
 import { SelectionManager } from '../../../util/SelectionManager';
+import { freeformScrollMode } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
-import { undoBatch, UndoManager } from '../../../util/UndoManager';
+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 { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
-import { PreviewCursor } from '../../PreviewCursor';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { SubCollectionViewProps } from '../CollectionSubView';
 import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './MarqueeView.scss';
-import * as React from 'react';
-import { freeformScrollMode } from '../../../util/SettingsManager';
 
 interface MarqueeViewProps {
     getContainerTransform: () => Transform;
@@ -51,16 +52,14 @@ export interface MarqueeViewBounds {
 }
 
 @observer
-export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> {
+export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps & MarqueeViewProps> {
     public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
         const ps = NumCast(pinDoc._freeform_scale, 1);
         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>) {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 697a11ccc..1e19964d7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, Lambda, observable, reaction } from 'mobx';
+import { action, computed, Lambda, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
@@ -27,6 +27,11 @@ export class CollectionGridView extends CollectionSubView() {
     @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
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
 
     @computed get numCols() {
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index a9782f699..f1fb68003 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -1,26 +1,26 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
+import { IReactionDisposer, action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, returnEmptyDoclist, returnTrue } from '../../../../Utils';
 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 { copyProps, emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { BranchingTrailManager } from '../../../util/BranchingTrailManager';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
+import { UndoStack } from '../../UndoStack';
 import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
 import { DocumentView } from '../../nodes/DocumentView';
 import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
-import { UndoStack } from '../../UndoStack';
 import { CollectionStackedTimeline } from '../CollectionStackedTimeline';
-import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
+import { CollectionSubView } from '../CollectionSubView';
 import './CollectionLinearView.scss';
 
 /**
@@ -33,24 +33,15 @@ import './CollectionLinearView.scss';
  */
 @observer
 export class CollectionLinearView extends CollectionSubView() {
-    @observable public addMenuToggle = React.createRef<HTMLInputElement>();
-    @observable private _selectedIndex = -1;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _widthDisposer?: IReactionDisposer;
     private _selectedDisposer?: IReactionDisposer;
 
-    _prevProps: SubCollectionViewProps;
-    @override _props: SubCollectionViewProps;
-    constructor(props: SubCollectionViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     componentWillUnmount() {
         this._dropDisposer?.();
         this._widthDisposer?.();
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 037786abf..b951a4b17 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Button } from 'browndash-components';
-import { action, computed } from 'mobx';
+import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
@@ -37,6 +37,11 @@ const resizerWidth = 8;
 
 @observer
 export class CollectionMulticolumnView extends CollectionSubView() {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     /**
      * @returns the list of layout documents whose width unit is
      * *, denoting that it will be displayed with a ratio, not fixed pixel, value
@@ -238,8 +243,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         return this.props.isContentActive?.() === false || childDocsActive === false
             ? false //
             : this.props.isDocumentActive?.() && childDocsActive
-            ? true
-            : undefined;
+              ? true
+              : undefined;
     };
     getDisplayDoc = (childLayout: Doc) => {
         const width = () => this.lookupPixels(childLayout);
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index bbf3481dd..f1c6c6e1e 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.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 } from '../../../../fields/Doc';
@@ -33,6 +33,11 @@ const resizerHeight = 8;
 
 @observer
 export class CollectionMultirowView extends CollectionSubView() {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     /**
      * @returns the list of layout documents whose width unit is
      * *, denoting that it will be displayed with a ratio, not fixed pixel, value
@@ -234,8 +239,8 @@ export class CollectionMultirowView extends CollectionSubView() {
         return this.props.isContentActive?.() === false || childDocsActive === false
             ? false //
             : this.props.isDocumentActive?.() && childDocsActive
-            ? true
-            : undefined;
+              ? true
+              : undefined;
     };
     getDisplayDoc = (layout: Doc) => {
         const height = () => this.lookupPixels(layout);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 276415d56..2546f5b02 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,13 +1,13 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, ObservableMap, observe, override } from 'mobx';
+import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 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';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
@@ -18,8 +18,9 @@ import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
 import { DocFocusOptions, DocumentView, DocumentViewProps } from '../../nodes/DocumentView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DefaultStyleProvider, StyleProp } from '../../StyleProvider';
-import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
+import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
@@ -57,6 +58,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     private _tableContentRef: HTMLDivElement | null = null;
     private _menuTarget = React.createRef<HTMLDivElement>();
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     static _rowHeight: number = 50;
     static _rowSingleLineHeight: number = 32;
     public static _minColWidth: number = 25;
@@ -79,18 +85,6 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _filterSearchValue: string = '';
     @observable _selectedCell: [Doc, number] | undefined = undefined;
 
-    _prevProps: SubCollectionViewProps;
-    @override _props: SubCollectionViewProps;
-    constructor(props: SubCollectionViewProps) {
-        super(props);
-        this._props = this._prevProps = props;
-        makeObservable(this);
-    }
-
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     // target HTMLelement portal for showing a popup menu to edit cell values.
     public get MenuTarget() {
         return this._menuTarget.current;
@@ -959,17 +953,12 @@ interface CollectionSchemaViewDocProps {
 }
 
 @observer
-class CollectionSchemaViewDoc extends React.Component<CollectionSchemaViewDocProps> {
-    _prevProps: CollectionSchemaViewDocProps;
-    @observable _props;
-    constructor(props: CollectionSchemaViewDocProps) {
+class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaViewDocProps> {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
+
     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) => {
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 737d7e0f4..5a3be826b 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -1,13 +1,13 @@
-import * as React from 'react';
 import { IconButton, Size } from 'browndash-components';
-import { computed, makeObservable, override } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
+import * as React from 'react';
 import { CgClose } from 'react-icons/cg';
 import { FaExternalLinkAlt } from 'react-icons/fa';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast } from '../../../../fields/Types';
-import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
@@ -30,16 +30,10 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps & SchemaRo
     }
     private _ref: HTMLDivElement | null = null;
 
-    _prevProps: FieldViewProps & SchemaRowBoxProps;
-    @override _props: FieldViewProps & SchemaRowBoxProps;
-    constructor(props: FieldViewProps & SchemaRowBoxProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     bounds = () => this._ref?.getBoundingClientRect();
 
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index bcfe2c232..85269028b 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,27 +1,28 @@
-import * as React from 'react';
-import Select, { MenuPlacement } from 'react-select';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
+import * as React from 'react';
 import DatePicker from 'react-datepicker';
+import Select from 'react-select';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
 import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast, Field } from '../../../../fields/Doc';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
 import { FInfo } from '../../../documents/Documents';
 import { DocFocusOrOpen } from '../../../util/DocumentManager';
 import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch, undoable } from '../../../util/UndoManager';
 import { EditableView } from '../../EditableView';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { DefaultStyleProvider } from '../../StyleProvider';
 import { Colors } from '../../global/globalEnums';
 import { OpenWhere } from '../../nodes/DocumentView';
-import { FieldView, FieldViewProps } from '../../nodes/FieldView';
-import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { FieldViewProps } from '../../nodes/FieldView';
 import { KeyValueBox } from '../../nodes/KeyValueBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { ColumnType, FInfotoColType } from './CollectionSchemaView';
 import './CollectionSchemaView.scss';
 
 export interface SchemaTableCellProps {
@@ -47,7 +48,12 @@ export interface SchemaTableCellProps {
 }
 
 @observer
-export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
+export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     static addFieldDoc = (doc: Doc, where: OpenWhere) => {
         DocFocusOrOpen(doc);
         return true;
@@ -95,12 +101,6 @@ 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;
@@ -182,16 +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 = undefined;
-
-    _props: React.PropsWithChildren<SchemaTableCellProps>;
-    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
+    @observable _previewRef: HTMLImageElement | undefined = undefined;
+
     choosePath(url: URL) {
         if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
         if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
@@ -251,16 +249,13 @@ export class SchemaImageCell extends React.Component<SchemaTableCellProps> {
 }
 
 @observer
-export class SchemaDateCell extends React.Component<SchemaTableCellProps> {
-    @observable _pickingDate: boolean = false;
-
-    @observable _props: React.PropsWithChildren<SchemaTableCellProps>;
-    constructor(props: React.PropsWithChildren<SchemaTableCellProps>) {
+export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
+    @observable _pickingDate: boolean = false;
     @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]);
@@ -282,11 +277,9 @@ 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>) {
+export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
@@ -306,11 +299,9 @@ 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>) {
+export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
@@ -352,11 +343,9 @@ 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>) {
+export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableCellProps> {
+    constructor(props: any) {
         super(props);
-        this._props = props;
         makeObservable(this);
     }
 
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index af22f41e1..b7376e901 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,13 +1,14 @@
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { LinkManager } from '../../util/LinkManager';
+import { SettingsManager } from '../../util/SettingsManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentView } from '../nodes/DocumentView';
 import { LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenu.scss';
 import { LinkMenuGroup } from './LinkMenuGroup';
-import * as React from 'react';
-import { SettingsManager } from '../../util/SettingsManager';
 
 interface Props {
     docView: DocumentView;
@@ -20,10 +21,10 @@ interface Props {
  * the outermost component for the link menu of a node that contains a list of its linked nodes
  */
 @observer
-export class LinkMenu extends React.Component<Props> {
+export class LinkMenu extends ObservableReactComponent<Props> {
     _editorRef = React.createRef<HTMLDivElement>();
     @observable _linkMenuRef = React.createRef<HTMLDivElement>();
-    constructor(props: Props) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d77525e04..06073b52c 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,23 +1,24 @@
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, observable, makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { Cast, DocCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents, copyProps } from '../../../Utils';
-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 { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
-import * as React from 'react';
+import { undoBatch } from '../../util/UndoManager';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { DocumentType } from '../../documents/DocumentTypes';
 
 interface LinkMenuItemProps {
     groupType: string;
@@ -50,20 +51,13 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
 }
 
 @observer
-export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
+export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
     private _drag = React.createRef<HTMLDivElement>();
     _editRef = React.createRef<HTMLDivElement>();
-
-    _prevProps: React.PropsWithChildren<LinkMenuItemProps>;
-    @observable _props: React.PropsWithChildren<LinkMenuItemProps>;
-    constructor(props: React.PropsWithChildren<LinkMenuItemProps>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     @observable private _showMore: boolean = false;
     @action toggleShowMore(e: React.PointerEvent) {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 0c671f7e3..567cf193e 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, makeObservable, observable, override, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from '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 { copyProps, emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -17,7 +17,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import './AudioBox.scss';
 import { DocFocusOptions } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -54,19 +54,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return FieldView.LayoutString(AudioBox, fieldKey);
     }
 
-    _prevProps: React.PropsWithChildren<FieldViewProps>;
-    @override _props: React.PropsWithChildren<FieldViewProps>;
-    constructor(props: React.PropsWithChildren<FieldViewProps>) {
+    public static Enabled = false;
+
+    constructor(props: any) {
         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
     static bottomControlsHeight = 20; // height of lower controls below timeline
 
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8e7a6914f..c97c879af 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,21 +1,21 @@
-import { action, computed, observable, makeObservable, reaction, runInAction, override } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { OmitKeys, numberRange } from '../../../Utils';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { copyProps, numberRange, OmitKeys } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SelectionManager } from '../../util/SelectionManager';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import './CollectionFreeFormDocumentView.scss';
 import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
-import * as React from 'react';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 
 export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
     x: number;
@@ -37,11 +37,8 @@ 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 }>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
     @observable X = this.props.x;
@@ -79,7 +76,7 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     w_Highlight = ()       => this.Highlight; // prettier-ignore
     w_Width = ()           => this.Width; // prettier-ignore
     w_Height = ()          => this.Height; // prettier-ignore
-    w_AutoDim = () => this.AutoDim;
+    w_AutoDim = ()         => this.AutoDim; // prettier-ignore
     w_Transition = ()      => this.Transition; // prettier-ignore
     w_DataTransition = ()  => this.DataTransition; // prettier-ignore
 
@@ -87,7 +84,6 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
 
     componentDidUpdate() {
-        copyProps(this);
         this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
     }
     render() {
@@ -124,6 +120,10 @@ export interface CollectionFreeFormDocumentViewProps {
 
 @observer
 export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
     get displayName() { // this makes mobx trace() statements more descriptive
         return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
     } // prettier-ignore
@@ -143,13 +143,6 @@ 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;
     }
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 62523ba00..de382fca5 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,10 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, makeObservable, observable, override } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { copyProps, emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
+import { DocUtils, Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -13,7 +14,6 @@ import './ComparisonBox.scss';
 import { DocumentView, DocumentViewProps } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
-import * as React from 'react';
 
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -21,20 +21,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         return FieldView.LayoutString(ComparisonBox, fieldKey);
     }
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
-
-    @observable _animating = '';
-
-    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
-    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
+    @observable _animating = '';
 
     @computed get clipWidth() {
         return NumCast(this.layoutDoc[this.clipWidthKey], 50);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c615e7ff0..e161b4c4c 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,17 +1,20 @@
-import { computed, makeObservable, observable, runInAction, untracked } from 'mobx';
+import { computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import * as XRegExp from 'xregexp';
+import { OmitKeys, Without, emptyPath } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
 import { ScriptField } from '../../../fields/ScriptField';
 import { Cast, StrCast } from '../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyPath, OmitKeys, Without } from '../../../Utils';
+import { InkingStroke } from '../InkingStroke';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { CollectionDockingView } from '../collections/CollectionDockingView';
+import { CollectionView } from '../collections/CollectionView';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView';
 import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox';
-import { CollectionView } from '../collections/CollectionView';
-import { InkingStroke } from '../InkingStroke';
 import { PresElementBox } from '../nodes/trails/PresElementBox';
 import { SearchBox } from '../search/SearchBox';
 import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo';
@@ -24,10 +27,8 @@ import './DocumentView.scss';
 import { EquationBox } from './EquationBox';
 import { FieldView, FieldViewProps } from './FieldView';
 import { FontIconBox } from './FontIconBox/FontIconBox';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { FunctionPlotBox } from './FunctionPlotBox';
 import { ImageBox } from './ImageBox';
-import { ImportElementBox } from './importBox/ImportElementBox';
 import { KeyValueBox } from './KeyValueBox';
 import { LabelBox } from './LabelBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
@@ -40,11 +41,11 @@ import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox';
 import { RecordingBox } from './RecordingBox';
 import { ScreenshotBox } from './ScreenshotBox';
 import { ScriptingBox } from './ScriptingBox';
-import { PresBox } from './trails/PresBox';
 import { VideoBox } from './VideoBox';
 import { WebBox } from './WebBox';
-import * as React from 'react';
-import * as XRegExp from 'xregexp';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
+import { ImportElementBox } from './importBox/ImportElementBox';
+import { PresBox } from './trails/PresBox';
 
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -114,29 +115,18 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
 }
 
 @observer
-export class DocumentContentsView extends React.Component<
+export class DocumentContentsView extends ObservableReactComponent<
     DocumentViewProps &
         FieldViewProps & {
             setHeight?: (height: number) => void;
             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;
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 9ec4d8c13..dfd610581 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -1,34 +1,28 @@
+import { Tooltip } from '@mui/material';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DocumentView } from './DocumentView';
-import { DocumentManager } from '../../util/DocumentManager';
-import { Transformer, ts } from '../../util/Scripting';
+import { factory } from 'typescript';
 import { Field } from '../../../fields/Doc';
-import { Tooltip } from '@mui/material';
-import { action, makeObservable, observable } from 'mobx';
 import { Id } from '../../../fields/FieldSymbols';
-import { factory } from 'typescript';
-import { LightboxView } from '../LightboxView';
+import { DocumentManager } from '../../util/DocumentManager';
+import { Transformer, ts } from '../../util/Scripting';
 import { SettingsManager } from '../../util/SettingsManager';
-import { copyProps } from '../../../Utils';
+import { LightboxView } from '../LightboxView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DocumentView } from './DocumentView';
 
 interface DocumentIconProps {
     view: DocumentView;
     index: number;
 }
 @observer
-export class DocumentIcon extends React.Component<DocumentIconProps> {
+export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     @observable _hovered = false;
-    _prevProps: DocumentIconProps;
-    @observable _props: DocumentIconProps;
-    constructor(props: DocumentIconProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     static get DocViews() {
         return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v._props.docViewPath())) : DocumentManager.Instance.DocumentViews;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 19b43fd52..165057d21 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -2,20 +2,21 @@ 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 { StopEvent, emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
-import { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, StopEvent } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { Hypothesis } from '../../util/HypothesisUtils';
 import { LinkManager } from '../../util/LinkManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import './DocumentLinksButton.scss';
 import { DocumentView } from './DocumentView';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { PinProps } from './trails';
-import * as React from 'react';
 
 interface DocumentLinksButtonProps {
     View: DocumentView;
@@ -44,26 +45,18 @@ export class DocButtonState {
     }
 }
 @observer
-export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
+export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksButtonProps> {
     private _linkButton = React.createRef<HTMLDivElement>();
     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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this._props.InMenu && this._props.StartLink) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 08ee708f7..343f770d5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,9 +1,11 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { Dropdown, DropdownType, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction, untracked } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, 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 * as React from 'react';
+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';
 import { Id } from '../../../fields/FieldSymbols';
@@ -15,12 +17,11 @@ 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 { 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';
-import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
+import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
+import { DocOptions, DocUtils, Docs, FInfo } from '../../documents/Documents';
 import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -32,13 +33,14 @@ import { SettingsManager } from '../../util/SettingsManager';
 import { SharingManager } from '../../util/SharingManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { UndoManager, undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { UndoStack } from '../UndoStack';
 import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
@@ -46,12 +48,11 @@ import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
 import { FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { KeyValueBox } from './KeyValueBox';
 import { LinkAnchorBox } from './LinkAnchorBox';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
-import * as React from 'react';
 const { Howl } = require('howler');
 
 interface Window {
@@ -253,20 +254,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _mainCont = React.createRef<HTMLDivElement>();
     private _titleRef = React.createRef<EditableView>();
     private _dropDisposer?: DragManager.DragDropDisposer;
-
-    @override _props: DocumentViewInternalProps;
-    _prevProps: DocumentViewInternalProps;
-
-    constructor(props: DocumentViewInternalProps) {
+    constructor(props: any) {
         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;
@@ -1329,19 +1321,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 }
 
 @observer
-export class DocumentView extends React.Component<DocumentViewProps> {
+export class DocumentView extends ObservableReactComponent<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() {
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 02ed56333..ff92c701f 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -1,5 +1,4 @@
-import EquationEditor from './formattedText/EquationEditor';
-import { action, makeObservable, override, reaction } from 'mobx';
+import { action, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Id } from '../../../fields/FieldSymbols';
@@ -11,7 +10,7 @@ import { ViewBoxBaseComponent } from '../DocComponent';
 import { LightboxView } from '../LightboxView';
 import './EquationBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
-import { copyProps } from '../../../Utils';
+import EquationEditor from './formattedText/EquationEditor';
 
 @observer
 export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -21,18 +20,11 @@ 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>) {
+    constructor(props: any) {
         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()))) {
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index d8b1f125c..5a8665aaf 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 { computed, makeObservable, observable, override } from 'mobx';
+import { computed, makeObservable, observable } 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 { copyProps, emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -45,16 +45,11 @@ 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>) {
+
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
     //
     // This controls whether fontIconButtons will display labels under their icons or not
     //
@@ -64,6 +59,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
     public static set ShowIconLabels(show: boolean) {
         Doc.UserDoc()._showLabel = show;
     }
+
     @observable noTooltip = false;
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index a04c27e01..29bffb583 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -1,15 +1,12 @@
 import functionPlot from 'function-plot';
-import { action, computed, makeObservable, override, reaction } from 'mobx';
+import { computed, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
-import { createSchema, listSpec, makeInterface } from '../../../fields/Schema';
+import { listSpec } 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';
@@ -27,19 +24,12 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     _plotId = '';
     _plotEle: 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);
         reaction(
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 091c1a32f..b1ccd38ba 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import * as React from 'react';
@@ -12,7 +12,7 @@ import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { copyProps, DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -55,19 +55,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
 
-    _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);
-    }
-
     protected createDropTarget = (ele: HTMLDivElement) => {
         this._dropDisposer?.();
         ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9aab53daf..73fdc3a23 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,26 +1,27 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
+import { returnAlways, returnTrue } from '../../../Utils';
 import { Doc, Field, FieldResult } from '../../../fields/Doc';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { DocCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { copyProps, returnAll, returnAlways, returnTrue } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { SetupDrag } from '../../util/DragManager';
-import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting';
+import { CompileScript, CompiledScript, ScriptOptions } from '../../util/Scripting';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { DocumentIconContainer } from './DocumentIcon';
 import { OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { ImageBox } from './ImageBox';
 import './KeyValueBox.scss';
 import { KeyValuePair } from './KeyValuePair';
-import * as React from 'react';
+import { FormattedTextBox } from './formattedText/FormattedTextBox';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -28,28 +29,20 @@ export type KVPScript = {
     onDelegate: boolean;
 };
 @observer
-export class KeyValueBox extends React.Component<FieldViewProps> {
+export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
     public static LayoutString() {
         return FieldView.LayoutString(KeyValueBox, 'data');
     }
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
 
     private _mainCont = React.createRef<HTMLDivElement>();
     private _keyHeader = React.createRef<HTMLTableHeaderCellElement>();
     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);
     }
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 40991f371..fd8d8ef56 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,21 +1,23 @@
-import { action, makeObservable, observable, toJS } from 'mobx';
+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';
-import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../Utils';
+import { DocCast } from '../../../fields/Types';
+import { DocumentOptions, FInfo } from '../../documents/Documents';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { EditableView } from '../EditableView';
 import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere, OpenWhereMod } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './DocumentView';
+import { FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
 import './KeyValueBox.scss';
 import './KeyValuePair.scss';
-import * as React from 'react';
-import { DocCast } from '../../../fields/Types';
-import { Tooltip } from '@mui/material';
-import { DocumentOptions, FInfo } from '../../documents/Documents';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 
 // Represents one row in a key value plane
 
@@ -29,22 +31,15 @@ export interface KeyValuePairProps {
     addDocTab: (doc: Doc, where: OpenWhere) => boolean;
 }
 @observer
-export class KeyValuePair extends React.Component<KeyValuePairProps> {
+export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
     @observable private isPointerOver = false;
     @observable public isChecked = false;
     private checkbox = React.createRef<HTMLInputElement>();
-
-    _prevProps: KeyValuePairProps;
-    @observable _props: KeyValuePairProps;
-    constructor(props: KeyValuePairProps) {
+    constructor(props:any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
 
     @action
     handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 52ca8b5b1..934bce448 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,11 +1,10 @@
-import { action, computed, makeObservable, observable, override } from 'mobx';
+import { action, computed, makeObservable, observable } 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 { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
@@ -31,16 +30,11 @@ 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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
-    componentDidUpdate() {
-        copyProps(this);
-    }
+
     componentDidMount() {
         this._props.setContentView?.(this);
     }
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index a7e905fca..b86ba72a0 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,9 +1,9 @@
-import { action, computed, makeObservable, observable, override } from 'mobx';
-import { observer } from 'mobx-react';
+import { action, computed, makeObservable } from 'mobx';
+import * as React from 'react';
+import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
 import { Doc } from '../../../fields/Doc';
 import { NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { LinkFollower } from '../../util/LinkFollower';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -12,11 +12,7 @@ import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkInfo } from './LinkDocPreview';
-import * as React from 'react';
-const {
-    default: { MEDIUM_GRAY },
-} = require('../global/globalCssVariables.module.scss');
-@observer
+const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(LinkAnchorBox, fieldKey);
@@ -26,14 +22,9 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _ref = React.createRef<HTMLDivElement>();
     _isOpen = false;
     _timeout: NodeJS.Timeout | undefined;
-    @observable _x = 0;
-    @observable _y = 0;
 
-    _prevProps: FieldViewProps;
-    @override _props: FieldViewProps;
-    constructor(props: FieldViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index acafd6d09..134f2e14a 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,10 +1,10 @@
-import * as React from 'react';
 import { Bezier } from 'bezier-js';
-import { computed, IReactionDisposer, makeObservable, observable, override, reaction } from 'mobx';
+import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { Id } from '../../../fields/FieldSymbols';
 import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { aggregateBounds, copyProps, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
+import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
 import { DocumentManager } from '../../util/DocumentManager';
 import { Transform } from '../../util/Transform';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
@@ -20,18 +20,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(LinkBox, fieldKey);
     }
 
-    _prevProps: FieldViewProps;
-    @override _props: FieldViewProps;
-    constructor(props: FieldViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     onClickScriptDisable = returnAlways;
     @computed get anchor1() {
         const anchor1 = DocCast(this.dataDoc.link_anchor_1);
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 108930ad9..fa7a55bc7 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -19,6 +19,7 @@ import { Transform } from '../../util/Transform';
 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>;
@@ -49,15 +50,7 @@ interface LinkDocPreviewProps {
     noPreview?: boolean;
 }
 @observer
-export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
-    _prevProps: LinkDocPreviewProps;
-    @observable _props: LinkDocPreviewProps;
-    constructor(props: LinkDocPreviewProps) {
-        super(props);
-        this._props = this._prevProps = props;
-        makeObservable(this);
-    }
-
+export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps> {
     _infoRef = React.createRef<HTMLDivElement>();
     _linkDocRef = React.createRef<HTMLDivElement>();
     @observable _targetDoc: Opt<Doc> = undefined;
@@ -66,6 +59,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     @observable _linkSrc: Opt<Doc> = undefined;
     @observable _toolTipText = '';
     @observable _hrefInd = 0;
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
 
     @action
     init() {
@@ -84,9 +81,9 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         this._toolTipText = '';
         this.updateHref();
     }
-    componentDidUpdate(props: any) {
-        copyProps(this);
-        if (props.linkSrc !== this._props.linkSrc || props.linkDoc !== this._props.linkDoc || props.hrefs !== this._props.hrefs) this.init();
+    componentDidUpdate(prevProps: Readonly<LinkDocPreviewProps>) {
+        copyProps(this, prevProps);
+        if (prevProps.linkSrc !== this._props.linkSrc || prevProps.linkDoc !== this._props.linkDoc || prevProps.hrefs !== this._props.hrefs) this.init();
     }
     componentDidMount() {
         this.init();
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index ee1ea3ceb..b458e5a28 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,9 +1,9 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, Opt } from '../../../../fields/Doc';
+import * as React from 'react';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
+import { Doc, Opt } from '../../../../fields/Doc';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 // import { GPTPopup, GPTPopupMode } from './../../GPTPopup/GPTPopup';
@@ -36,7 +36,6 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     constructor(props: Readonly<{}>) {
         super(props);
-
         MapAnchorMenu.Instance = this;
         MapAnchorMenu.Instance._canFade = false;
     }
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 98a302834..a420e0101 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,24 +1,24 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import BingMapsReact from 'bingmaps-react';
 import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+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 { DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { copyProps, 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 { 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 { FormattedTextBox } from '../formattedText/FormattedTextBox';
@@ -67,18 +67,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [key: string]: IReactionDisposer } = {};
 
-    _unmounting = false;
-    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
-    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
+    _unmounting = false;
 
     @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @computed get allSidebarDocs() {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a453210eb..213f88177 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,8 +1,9 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
@@ -10,7 +11,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 { copyProps, emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -31,7 +32,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { PinProps, PresBox } from './trails';
-import * as React from 'react';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -58,11 +58,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
     }
 
-    _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;
@@ -72,9 +69,6 @@ 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) {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a4f4897ba..ebe86318d 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,15 +1,15 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction, untracked } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx';
 import { observer } from 'mobx-react';
 import { basename } from 'path';
+import * as React from 'react';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 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 { copyProps, emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
+import { 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';
@@ -68,19 +68,12 @@ 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[]>();
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 995f9f6e0..2f92f1edb 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import * as WebRequest from 'web-request';
@@ -13,7 +13,7 @@ import { listSpec } from '../../../fields/Schema';
 import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -98,19 +98,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
 
-    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
-    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
     constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
         this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     @action
     search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
         if (!this._searching && !clear) {
diff --git a/src/client/views/nodes/audio/AudioWaveform.tsx b/src/client/views/nodes/audio/AudioWaveform.tsx
index 1b1a85800..01392c4a5 100644
--- a/src/client/views/nodes/audio/AudioWaveform.tsx
+++ b/src/client/views/nodes/audio/AudioWaveform.tsx
@@ -1,15 +1,16 @@
 import axios from 'axios';
-import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { computed, IReactionDisposer, makeObservable, 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 { numberRange } from '../../../../Utils';
 import { Colors } from './../../global/globalEnums';
 import './AudioWaveform.scss';
 import { WaveCanvas } from './WaveCanvas';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 
 /**
  * AudioWaveform
@@ -35,21 +36,15 @@ export interface AudioWaveformProps {
 }
 
 @observer
-export class AudioWaveform extends React.Component<AudioWaveformProps> {
+export class AudioWaveform extends ObservableReactComponent<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>) {
+
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     get waveHeight() {
         return Math.max(50, this._props.PanelHeight);
     }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index e2cceb906..555b752f0 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -2,22 +2,23 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from '../../../../fields/Types';
-import { copyProps, emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { Cast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { Transform } from '../../../util/Transform';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { OpenWhere } from '../DocumentView';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
-import * as React from 'react';
-import { Transform } from '../../../util/Transform';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -92,7 +93,7 @@ interface IDashFieldViewInternal {
 }
 
 @observer
-export class DashFieldViewInternal extends React.Component<IDashFieldViewInternal> {
+export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldViewInternal> {
     _reactionDisposer: IReactionDisposer | undefined;
     _textBoxDoc: Doc;
     _fieldKey: string;
@@ -100,11 +101,8 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     @observable _dashDoc: Doc | undefined = undefined;
     @observable _expanded = false;
 
-    _prevProps: IDashFieldViewInternal;
-    @observable _props: IDashFieldViewInternal;
     constructor(props: IDashFieldViewInternal) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
         this._fieldKey = this._props.fieldKey;
         this._textBoxDoc = this._props.tbox.Document;
@@ -115,9 +113,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             this._dashDoc = this._props.tbox.Document;
         }
     }
-    componentDidUpdate(prevProps: Readonly<IDashFieldViewInternal>, prevState: Readonly<{}>, snapshot?: any): void {
-        copyProps(this);
-    }
+
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 56bf15b09..997c3f86d 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, makeObservable, observable, ObservableSet, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { baseKeymap, selectAll } from 'prosemirror-commands';
 import { history } from 'prosemirror-history';
@@ -14,7 +14,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, DocData, ForceServerWrite, UpdatingFromServer } 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';
@@ -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, copyProps, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+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';
@@ -55,7 +55,7 @@ import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
-import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
+import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
 import { DashDocView } from './DashDocView';
@@ -208,20 +208,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    _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.
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 7de1eb717..4881070fd 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,21 +1,22 @@
-import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { lift, wrapIn } from 'prosemirror-commands';
 import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model';
 import { wrapInList } from 'prosemirror-schema-list';
 import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
+import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast, Cast, StrCast } from '../../../../fields/Types';
-import { copyProps, numberRange } from '../../../../Utils';
+import { numberRange } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { LinkManager } from '../../../util/LinkManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { EquationBox } from '../EquationBox';
 import { FieldViewProps } from '../FieldView';
 import { FormattedTextBox } from './FormattedTextBox';
@@ -63,18 +64,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private showLinkDropdown: boolean = false;
 
     _reaction: IReactionDisposer | undefined;
-    _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);
-            this._canFade = false;
-            this.Pinned = true;
-        });
+        RichTextMenu.Instance = this;
+        this.updateMenu(undefined, undefined, props, this.layoutDoc);
+        this._canFade = false;
+        this.Pinned = true;
     }
 
     @computed get noAutoLink() {
@@ -111,9 +107,6 @@ 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(),
@@ -368,7 +361,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) => {
@@ -377,7 +370,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) {
@@ -386,7 +379,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) {
@@ -395,7 +388,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
@@ -436,7 +429,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) {
@@ -818,22 +811,15 @@ interface ButtonDropdownProps {
 }
 
 @observer
-export class ButtonDropdown extends React.Component<ButtonDropdownProps> {
+export class ButtonDropdown extends ObservableReactComponent<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>) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
-
     componentDidMount() {
         document.addEventListener('pointerdown', this.onBlur);
     }
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 9fcb496b8..ec5d090dd 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -1,12 +1,13 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } 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 { copyProps, emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { DocumentManager } from '../../../util/DocumentManager';
@@ -24,7 +25,6 @@ import { StyleProp } from '../../StyleProvider';
 import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
-import * as React from 'react';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -41,19 +41,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     readonly expandViewHeight = 100;
     readonly collapsedHeight = 35;
 
-    @observable _dragging = false;
-
-    _prevProps: FieldViewProps;
-    @override _props: FieldViewProps;
-    constructor(props: FieldViewProps) {
+    constructor(props: any) {
         super(props);
-        this._props = this._prevProps = props;
         makeObservable(this);
     }
 
-    componentDidUpdate() {
-        copyProps(this);
-    }
+    @observable _dragging = false;
 
     // the presentation view that renders this slide
     @computed get presBoxView() {
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 0627876fa..a8c83ded6 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,12 +1,12 @@
-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, makeObservable, observable, ObservableMap, override, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import { ColorResult } from 'react-color';
+import { Utils, returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
-import { copyProps, returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
-import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
+import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
@@ -23,18 +23,12 @@ 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)';
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index a581e9df6..e342c25b3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,13 +1,16 @@
 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';
+import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
+import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Height } from '../../../fields/DocSymbols';
 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, copyProps, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -16,15 +19,13 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
-import { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
+import { ObservableReactComponent } from '../ObservableReactComponent';
 import { StyleProp } from '../StyleProvider';
 import { AnchorMenu } from './AnchorMenu';
 import { Annotation } from './Annotation';
 import { GPTPopup } from './GPTPopup/GPTPopup';
 import './PDFViewer.scss';
-import * as React from 'react';
-import * as Pdfjs from 'pdfjs-dist';
-import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
@@ -49,14 +50,11 @@ interface IViewerProps extends FieldViewProps {
  * Handles rendering and virtualization of the pdf
  */
 @observer
-export class PDFViewer extends React.Component<IViewerProps> {
+export class PDFViewer extends ObservableReactComponent<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
 
-    _prevProps: IViewerProps;
-    @observable _props: IViewerProps;
-    constructor(props: IViewerProps) {
+    constructor(props: any) {
         super(props);
-        this._prevProps = this._props = props;
         makeObservable(this);
     }
 
@@ -66,10 +64,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @observable _showWaiting = true;
     @observable Index: number = -1;
 
-    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
-- 
cgit v1.2.3-70-g09d2

