From ab1688962875073fa59a4a97303d0e0f0398ba5d Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 30 Nov 2023 15:23:08 -0500
Subject: fixed not fitting width for carousel views.

---
 src/client/views/collections/CollectionCarouselView.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 33a92d406..bcccf0044 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -2,13 +2,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { computed } from 'mobx';
 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 { emptyFunction, returnFalse, returnOne, returnZero, StopEvent } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
+import { StyleProp } from '../StyleProvider';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
-import { StyleProp } from '../StyleProvider';
 import './CollectionCarouselView.scss';
 import { CollectionSubView } from './CollectionSubView';
 
@@ -59,6 +59,8 @@ export class CollectionCarouselView extends CollectionSubView() {
                         {...this.props}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
+                        shouldNotScale={undefined}
+                        layout_fitWidth={undefined}
                         setContentView={undefined}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
-- 
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/CollectionCarouselView.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/CollectionCarouselView.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 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/CollectionCarouselView.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

