From 2e56299ddfca9cc9e8466b45170ce3f05ee64f15 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Dec 2023 12:49:58 -0500
Subject: fixed overlay view and scriptingRepl and all observable Doc arrays to
 be shallowly observed.

---
 src/client/views/ScriptingRepl.tsx | 71 +++++++++++++++++++++++++++++---------
 1 file changed, 54 insertions(+), 17 deletions(-)

(limited to 'src/client/views/ScriptingRepl.tsx')

diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 9966dbced..8251d20dc 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 } from 'mobx';
+import { action, observable, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DocumentManager } from '../util/DocumentManager';
@@ -10,24 +10,40 @@ import { undoable } from '../util/UndoManager';
 import { DocumentIconContainer } from './nodes/DocumentIcon';
 import { OverlayView } from './OverlayView';
 import './ScriptingRepl.scss';
+import { copyProps } from '../../Utils';
 
-@observer
-export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: () => void; value: { [key: string]: any }; name?: string }> {
+interface ReplProps {
+    scrollToBottom: () => void;
+    value: { [key: string]: any };
+    name?: string;
+}
+export class ScriptingObjectDisplay extends React.Component<ReplProps> {
     @observable collapsed = true;
 
+    _prevProps: ReplProps;
+    @observable _props: ReplProps;
+    constructor(props: ReplProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate(): void {
+        copyProps(this);
+    }
+
     @action
     toggle = () => {
         this.collapsed = !this.collapsed;
-        this.props.scrollToBottom();
+        this._props.scrollToBottom();
     };
 
     render() {
-        const val = this.props.value;
+        const val = this._props.value;
         const proto = Object.getPrototypeOf(val);
         const name = (proto && proto.constructor && proto.constructor.name) || String(val);
-        const title = this.props.name ? (
+        const title = this._props.name ? (
             <>
-                <b>{this.props.name} : </b>
+                <b>{this._props.name} : </b>
                 {name}
             </>
         ) : (
@@ -53,7 +69,7 @@ export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: ()
                     </div>
                     <div className="scriptingObject-fields">
                         {Object.keys(val).map(key => (
-                            <ScriptingValueDisplay {...this.props} name={key} />
+                            <ScriptingValueDisplay {...this._props} name={key} />
                         ))}
                     </div>
                 </div>
@@ -62,17 +78,32 @@ export class ScriptingObjectDisplay extends React.Component<{ scrollToBottom: ()
     }
 }
 
+interface replValueProps {
+    scrollToBottom: () => void;
+    value: any;
+    name?: string;
+}
 @observer
-export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: () => void; value: any; name?: string }> {
+export class ScriptingValueDisplay extends React.Component<replValueProps> {
+    _prevProps: replValueProps;
+    @observable _props: replValueProps;
+    constructor(props: replValueProps) {
+        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;
+        const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
         if (typeof val === 'object') {
-            return <ScriptingObjectDisplay scrollToBottom={this.props.scrollToBottom} value={val} name={this.props.name} />;
+            return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
         } else if (typeof val === 'function') {
             const name = '[Function]';
-            const title = this.props.name ? (
+            const title = this._props.name ? (
                 <>
-                    <b>{this.props.name} : </b>
+                    <b>{this._props.name} : </b>
                     {name}
                 </>
             ) : (
@@ -81,9 +112,9 @@ export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: ()
             return <div className="scriptingObject-leaf">{title}</div>;
         } else {
             const name = String(val);
-            const title = this.props.name ? (
+            const title = this._props.name ? (
                 <>
-                    <b>{this.props.name} : </b>
+                    <b>{this._props.name} : </b>
                     {name}
                 </>
             ) : (
@@ -96,6 +127,11 @@ export class ScriptingValueDisplay extends React.Component<{ scrollToBottom: ()
 
 @observer
 export class ScriptingRepl extends React.Component {
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private commands: { command: string; result: any }[] = [];
     private commandsHistory: string[] = [];
 
@@ -136,7 +172,8 @@ export class ScriptingRepl extends React.Component {
                                     const m = parseInt(match[1]);
                                     usedDocuments.push(m);
                                 } else {
-                                    return ts.createPropertyAccess(ts.createIdentifier('args'), node);
+                                    return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('args'), node);
+                                    //  ts.createPropertyAccess(ts.createIdentifier('args'), node);
                                 }
                             }
                         }
@@ -156,7 +193,7 @@ export class ScriptingRepl extends React.Component {
             case 'Enter': {
                 e.stopPropagation();
                 const docGlobals: { [name: string]: any } = {};
-                DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.props.Document));
+                DocumentManager.Instance.DocumentViews.forEach((dv, i) => (docGlobals[`d${i}`] = dv.Document));
                 const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals);
                 const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: 'any' }, transformer: this.getTransformer(), globals });
                 if (!script.compiled) {
-- 
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/ScriptingRepl.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


From cebe9d2a567c20b99c8c394cfa598ee9d4d53ece Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Dec 2023 00:07:52 -0500
Subject: a bunch more fixes to making things observable.  fixed calling
 super.componentDidUpdate on subsclasses

---
 src/Utils.ts                                       |  17 +--
 src/client/util/BranchingTrailManager.tsx          |  12 +-
 src/client/util/CaptureManager.tsx                 |   3 +-
 src/client/util/GroupManager.tsx                   |   6 +-
 src/client/util/PingManager.ts                     |   3 +-
 src/client/util/RTFMarkup.tsx                      |   3 +-
 src/client/util/ReplayMovements.ts                 |   3 +-
 src/client/util/SelectionManager.ts                |  10 +-
 src/client/util/ServerStats.tsx                    |   3 +-
 src/client/util/SharingManager.tsx                 |   3 +-
 src/client/util/TrackMovements.ts                  |   4 +-
 src/client/util/reportManager/ReportManager.tsx    |   3 +-
 src/client/views/ContextMenu.tsx                   |   2 +-
 src/client/views/DictationOverlay.tsx              |   3 +-
 src/client/views/DocumentButtonBar.tsx             | 133 ++-------------------
 src/client/views/EditableView.tsx                  |   1 +
 src/client/views/GestureOverlay.tsx                |  17 +--
 src/client/views/InkStrokeProperties.ts            |   3 +-
 src/client/views/LightboxView.tsx                  |   3 +-
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/MarqueeAnnotator.tsx              |   2 +-
 src/client/views/ObservableReactComponent.tsx      |  10 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 src/client/views/ScriptBox.tsx                     |   3 +-
 src/client/views/ScriptingRepl.tsx                 |  37 ++----
 src/client/views/animationtimeline/Region.tsx      |   3 +-
 .../views/animationtimeline/TimelineMenu.tsx       |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   4 +-
 src/client/views/collections/CollectionMenu.tsx    |   7 +-
 .../collections/CollectionStackedTimeline.tsx      |   4 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionView.tsx    |   1 -
 src/client/views/collections/TabDocView.tsx        |   1 +
 src/client/views/collections/TreeView.tsx          |   1 +
 .../CollectionFreeFormInfoState.tsx                |   3 +-
 .../CollectionFreeFormInfoUI.tsx                   |  14 +--
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   5 +-
 .../CollectionMulticolumnView.tsx                  |  78 ++++++------
 .../CollectionMultirowView.tsx                     |  74 ++++++------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   3 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/FunctionPlotBox.tsx         |   2 +-
 src/client/views/nodes/ImageBox.tsx                |   3 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   4 +-
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx    |   5 +-
 src/client/views/nodes/PDFBox.tsx                  |   2 +-
 .../nodes/PhysicsBox/PhysicsSimulationBox.tsx      |   3 +-
 .../nodes/PhysicsBox/PhysicsSimulationWeight.tsx   |   3 +-
 src/client/views/nodes/RadialMenu.tsx              |   6 +-
 src/client/views/nodes/ScreenshotBox.tsx           |  13 +-
 src/client/views/nodes/ScriptingBox.tsx            |   3 +-
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/nodes/WebBox.tsx                  |   2 +-
 .../nodes/formattedText/DashDocCommentView.tsx     |   2 +-
 .../views/nodes/formattedText/EquationEditor.tsx   |   2 +-
 .../views/nodes/formattedText/EquationView.tsx     |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/nodes/trails/PresBox.tsx          |   3 +-
 src/client/views/pdf/AnchorMenu.tsx                |   2 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   6 +-
 src/client/views/search/CheckBox.scss              |  58 ---------
 src/client/views/search/CheckBox.tsx               | 130 --------------------
 src/client/views/search/CollectionFilters.scss     |  20 ----
 src/client/views/search/CollectionFilters.tsx      |  83 -------------
 src/client/views/search/IconBar.scss               |  10 --
 src/client/views/search/IconBar.tsx                |  73 -----------
 src/client/views/search/IconButton.scss            |  53 --------
 src/client/views/search/IconButton.tsx             | 118 ------------------
 src/client/views/search/NaviconButton.scss         |  69 -----------
 src/client/views/search/NaviconButton.tsx          |  37 ------
 src/client/views/search/SearchBox.scss             |   1 -
 src/client/views/search/SearchBox.tsx              |   3 +-
 src/client/views/search/ToggleBar.scss             |  41 -------
 src/client/views/search/ToggleBar.tsx              |  85 -------------
 src/fields/URLField.ts                             |   2 +-
 src/server/DashUploadUtils.ts                      |   4 +-
 76 files changed, 224 insertions(+), 1126 deletions(-)
 delete mode 100644 src/client/views/search/CheckBox.scss
 delete mode 100644 src/client/views/search/CheckBox.tsx
 delete mode 100644 src/client/views/search/CollectionFilters.scss
 delete mode 100644 src/client/views/search/CollectionFilters.tsx
 delete mode 100644 src/client/views/search/IconBar.scss
 delete mode 100644 src/client/views/search/IconBar.tsx
 delete mode 100644 src/client/views/search/IconButton.scss
 delete mode 100644 src/client/views/search/IconButton.tsx
 delete mode 100644 src/client/views/search/NaviconButton.scss
 delete mode 100644 src/client/views/search/NaviconButton.tsx
 delete mode 100644 src/client/views/search/ToggleBar.scss
 delete mode 100644 src/client/views/search/ToggleBar.tsx

(limited to 'src/client/views/ScriptingRepl.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 3a2bbf9a1..5f9475f23 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -7,7 +7,7 @@ import { DocumentType } from './client/documents/DocumentTypes';
 import { Colors } from './client/views/global/globalEnums';
 import { Message } from './server/Message';
 import * as Color from 'color';
-import { action, makeObservable } from 'mobx';
+import { action } from 'mobx';
 
 export namespace Utils {
     export let CLICK_TIME = 300;
@@ -56,10 +56,6 @@ export namespace Utils {
         return uuid.v5(seed, uuid.v5.URL);
     }
 
-    export function GenerateMongoId(id: string): string {
-        return id.length !== 36 ? Utils.GenerateDeterministicGuid(id) : id;
-    }
-
     export function GuestID() {
         return '__guest__';
     }
@@ -440,17 +436,6 @@ export namespace Utils {
         socket.on(message, (room: any) => handler(socket, room));
     }
 }
-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
-    thing._prevProps = thing.props;
-}
 
 export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } {
     const omit: any = { ...obj };
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index a224b84f4..11f16493f 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
@@ -15,13 +15,14 @@ export class BranchingTrailManager extends React.Component {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         if (!BranchingTrailManager.Instance) {
             BranchingTrailManager.Instance = this;
         }
     }
 
     setupUi = () => {
-        OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail'});
+        OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
         // OverlayView.Instance.forceUpdate();
         console.log(OverlayView.Instance);
         // let hi = Docs.Create.TextDocument("beee", {
@@ -30,11 +31,10 @@ export class BranchingTrailManager extends React.Component {
         // })
         // hi.overlayX = 100;
         // hi.overlayY = 100;
-        
+
         // Doc.AddToMyOverlay(hi);
         console.log(DocumentManager._overlayViews);
     };
-    
 
     // stack of the history
     @observable private slideHistoryStack: String[] = [];
@@ -69,7 +69,7 @@ export class BranchingTrailManager extends React.Component {
         if (this.prevPresId === null || this.prevPresId !== presId) {
             Doc.UserDoc().isBranchingMode = true;
             this.setPrevPres(presId);
-            
+
             // REVERT THE SET
             const stringified = [presId, targetDocId].toString();
             if (this.containsSet.has([presId, targetDocId].toString())) {
@@ -98,7 +98,7 @@ export class BranchingTrailManager extends React.Component {
 
         const newStack = this.slideHistoryStack.slice(0, removeIndex);
         const removed = this.slideHistoryStack.slice(removeIndex);
-        
+
         this.setSlideHistoryStack(newStack);
 
         removed.forEach(info => this.containsSet.delete(info.toString()));
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 271cf7495..071fc1ee9 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../fields/Doc';
@@ -20,6 +20,7 @@ export class CaptureManager extends React.Component<{}> {
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         CaptureManager.Instance = this;
     }
 
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index c8c93b7d0..90f65b648 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, IconButton, Size, Type } from 'browndash-components';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
@@ -17,6 +17,7 @@ import './GroupManager.scss';
 import { GroupMemberView } from './GroupMemberView';
 import { SettingsManager } from './SettingsManager';
 import { SharingManager, User } from './SharingManager';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
 
 /**
  * Interface for options for the react-select component
@@ -27,7 +28,7 @@ export interface UserOptions {
 }
 
 @observer
-export class GroupManager extends React.Component<{}> {
+export class GroupManager extends ObservableReactComponent<{}> {
     static Instance: GroupManager;
     @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
     @observable private users: string[] = []; // list of users populated from the database.
@@ -41,6 +42,7 @@ export class GroupManager extends React.Component<{}> {
 
     constructor(props: Readonly<{}>) {
         super(props);
+        makeObservable(this);
         GroupManager.Instance = this;
     }
 
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 4dd2fcd35..865f8bc02 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, makeObservable, observable, runInAction } from 'mobx';
 import { Networking } from '../Network';
 import { CurrentUserUtils } from './CurrentUserUtils';
 export class PingManager {
@@ -33,6 +33,7 @@ export class PingManager {
     private _interval: NodeJS.Timeout | null = null;
     INTERVAL_SECONDS = 1;
     constructor() {
+        makeObservable(this);
         PingManager._instance = this;
         this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
     }
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 495af6abd..f96d8a5df 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
@@ -23,6 +23,7 @@ export class RTFMarkup extends React.Component<{}> {
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         RTFMarkup.Instance = this;
     }
 
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index d99630f82..b881f18b4 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
 import { Doc, IdToDoc } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
@@ -19,6 +19,7 @@ export class ReplayMovements {
         return ReplayMovements._instance;
     }
     constructor() {
+        makeObservable(this);
         // init the global instance
         ReplayMovements._instance = this;
 
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4bd6647c0..07bbde36c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -31,8 +31,7 @@ export class SelectionManager {
         this.Instance.SelectedSchemaDocument = doc;
     };
 
-    @action
-    public static SelectView = (docView: DocumentView | undefined, extendSelection: boolean): void => {
+    public static SelectView = action((docView: DocumentView | undefined, extendSelection: boolean): void => {
         if (!docView) this.DeselectAll();
         else if (!docView.SELECTED) {
             if (!extendSelection) this.DeselectAll();
@@ -40,16 +39,15 @@ export class SelectionManager {
             docView.SELECTED = true;
             docView._props.whenChildContentsActiveChanged(true);
         }
-    };
+    });
 
-    @action
-    public static DeselectView = (docView?: DocumentView): void => {
+    public static DeselectView = action((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);
         }
-    };
+    });
 
     public static DeselectAll = (except?: Doc): void => {
         const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index 7d5e0458d..c8df9182d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
@@ -33,6 +33,7 @@ export class ServerStats extends React.Component<{}> {
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         ServerStats.Instance = this;
     }
 
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 7176b568e..ac7de9872 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, IconButton, Size, Type } from 'browndash-components';
 import { concat, intersection } from 'lodash';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import Select from 'react-select';
@@ -119,6 +119,7 @@ export class SharingManager extends React.Component<{}> {
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         SharingManager.Instance = this;
     }
 
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index 0e56ee1bc..0b197cf9a 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,4 +1,4 @@
-import { IReactionDisposer, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
 import { NumCast } from '../../fields/Types';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -40,7 +40,7 @@ export class TrackMovements {
     constructor() {
         // init the global instance
         TrackMovements._instance = this;
-
+        makeObservable(this);
         // init the instance variables
         this.currentPresentation = TrackMovements.NULL_PRESENTATION;
         this.tracking = false;
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 738902a31..0c49aeed4 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -3,7 +3,7 @@ import * as uuid from 'uuid';
 import '.././SettingsManager.scss';
 import './ReportManager.scss';
 import ReactLoading from 'react-loading';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
 import { CgClose } from 'react-icons/cg';
 import { HiOutlineArrowLeft } from 'react-icons/hi';
@@ -103,6 +103,7 @@ export class ReportManager extends React.Component<{}> {
 
     constructor(props: {}) {
         super(props);
+        makeObservable(this);
         ReportManager.Instance = this;
 
         // initializing Github connection
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index e55bf24a1..8dcdd80e5 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -37,8 +37,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
 
     constructor(props: any) {
         super(props);
-        ContextMenu.Instance = this;
         makeObservable(this);
+        ContextMenu.Instance = this;
     }
 
     public setIgnoreEvents(ignore: boolean) {
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index 0bdcdc303..e098bc361 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -1,4 +1,4 @@
-import { computed, observable, runInAction } from 'mobx';
+import { computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DictationManager } from '../util/DictationManager';
@@ -19,6 +19,7 @@ export class DictationOverlay extends React.Component {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         DictationOverlay.Instance = this;
     }
 
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 50ca3af06..47829d12a 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -3,32 +3,26 @@ 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 { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
-import { Cast, DocCast, NumCast } from '../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
-import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Cast, DocCast } from '../../fields/Types';
+import { DocUtils } from '../documents/Documents';
 import { DragManager } from '../util/DragManager';
 import { IsFollowLinkScript } from '../util/LinkFollower';
 import { SelectionManager } from '../util/SelectionManager';
 import { SharingManager } from '../util/SharingManager';
-import { undoBatch, UndoManager } from '../util/UndoManager';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { TabDocView } from './collections/TabDocView';
+import { UndoManager, undoBatch } from '../util/UndoManager';
 import './DocumentButtonBar.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+import { TabDocView } from './collections/TabDocView';
 import { Colors } from './global/globalEnums';
 import { LinkPopup } from './linking/LinkPopup';
-import { MetadataEntryMenu } from './MetadataEntryMenu';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
-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;
@@ -60,10 +54,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     public static hasPushedHack = false;
     public static hasPulledHack = false;
 
-    constructor(props: { views: () => (DocumentView | undefined)[] }) {
+    constructor(props: any) {
         super(props);
-        DocumentButtonBar.Instance = this;
         makeObservable(this);
+        DocumentButtonBar.Instance = this;
     }
 
     public startPullOutcome = action((success: boolean) => {
@@ -115,103 +109,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         return this._props.views()?.[0];
     }
 
-    @computed
-    get considerGoogleDocsPush() {
-        const targetDoc = this.view0?.Document;
-        const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
-        const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none';
-        return !targetDoc ? null : (
-            <Tooltip
-                title={
-                    <>
-                        <div className="dash-tooltip">{`${published ? 'Push' : 'Publish'} to Google Docs`}</div>
-                    </>
-                }>
-                <div
-                    className="documentButtonBar-button"
-                    style={{ animation }}
-                    onClick={async () => {
-                        await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
-                        !published && runInAction(() => (this.isAnimatingPulse = true));
-                        DocumentButtonBar.hasPushedHack = false;
-                        targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
-                    }}>
-                    <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? 'sm' : 'xs'} />
-                </div>
-            </Tooltip>
-        );
-    }
-
-    @computed
-    get considerGoogleDocsPull() {
-        const targetDoc = this.view0?.Document;
-        const dataDoc = targetDoc && Doc.GetProto(targetDoc);
-        const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none';
-
-        const title = (() => {
-            switch (this.openHover) {
-                default:
-                case UtilityButtonState.Default:
-                    return `${!dataDoc?.googleDocUnchanged ? 'Pull from' : 'Fetch'} Google Docs`;
-                case UtilityButtonState.OpenRight:
-                    return 'Open in Right Split';
-                case UtilityButtonState.OpenExternally:
-                    return 'Open in new Browser Tab';
-            }
-        })();
-
-        return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : (
-            <Tooltip title={<div className="dash-tooltip">{title}</div>}>
-                <div
-                    className="documentButtonBar-button"
-                    style={{ backgroundColor: this.pullColor }}
-                    onPointerEnter={action(e => {
-                        if (e.altKey) {
-                            this.openHover = UtilityButtonState.OpenExternally;
-                        } else if (e.shiftKey) {
-                            this.openHover = UtilityButtonState.OpenRight;
-                        }
-                    })}
-                    onPointerLeave={action(() => (this.openHover = UtilityButtonState.Default))}
-                    onClick={async e => {
-                        const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
-                        if (e.shiftKey) {
-                            e.preventDefault();
-                            let googleDoc = await Cast(dataDoc.googleDoc, Doc);
-                            if (!googleDoc) {
-                                const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, data_useCors: false };
-                                googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
-                                dataDoc.googleDoc = googleDoc;
-                            }
-                            CollectionDockingView.AddSplit(googleDoc, OpenWhereMod.right);
-                        } else if (e.altKey) {
-                            e.preventDefault();
-                            window.open(googleDocUrl);
-                        } else {
-                            this.clearPullColor();
-                            DocumentButtonBar.hasPulledHack = false;
-                            targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
-                            dataDoc.googleDocUnchanged && runInAction(() => (this.isAnimatingFetch = true));
-                        }
-                    }}>
-                    <FontAwesomeIcon
-                        className="documentdecorations-icon"
-                        size="sm"
-                        style={{ WebkitAnimation: animation, MozAnimation: animation }}
-                        icon={(() => {
-                            // prettier-ignore
-                            switch (this.openHover) {
-                                default:
-                                case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch;
-                                case UtilityButtonState.OpenRight: return 'arrow-alt-circle-right';
-                                case UtilityButtonState.OpenExternally: return 'share';
-                            }
-                        })()}
-                    />
-                </div>
-            </Tooltip>
-        );
-    }
     @observable subFollow = '';
     @computed
     get followLinkButton() {
@@ -579,8 +476,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
         if (!doc || !this.view0) return null;
 
         const isText = () => doc[this.view0!.LayoutFieldKey] instanceof RichTextField;
-        const considerPull = () => isText() && this.considerGoogleDocsPull;
-        const considerPush = () => isText() && this.considerGoogleDocsPush;
         return (
             <div className="documentButtonBar">
                 <div className="documentButtonBar-button">
@@ -608,14 +503,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
                 <div className="documentButtonBar-button">{this.pinButton}</div>
                 <div className="documentButtonBar-button">{this.recordButton}</div>
                 {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : <div className="documentButtonBar-button">{this.shareButton}</div>}
-                {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
-                    <div className="documentButtonBar-button" style={{ display: !considerPush() ? 'none' : '' }}>
-                        {this.considerGoogleDocsPush}
-                    </div>
-                )}
-                <div className="documentButtonBar-button" style={{ display: !considerPull() ? 'none' : '' }}>
-                    {this.considerGoogleDocsPull}
-                </div>
                 <div className="documentButtonBar-button">{this.menuButton}</div>
             </div>
         );
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 836a184eb..73ac1b032 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -94,6 +94,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> {
     }
 
     componentDidUpdate(prevProps: Readonly<EditableProps>) {
+        super.componentDidUpdate(prevProps);
         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/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 214da5541..33cda6a62 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 import * as fitCurve from 'fit-curve';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx';
 import { observer } from 'mobx-react';
 import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils';
 import { Doc, Opt } from '../../fields/Doc';
@@ -31,12 +31,13 @@ import {
 } from './InkingStroke';
 import { checkInksToGroup } from './global/globalScripts';
 import { DocumentView } from './nodes/DocumentView';
+import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface GestureOverlayProps {
     isActive: boolean;
 }
 @observer
-export class GestureOverlay extends React.Component<React.PropsWithChildren<GestureOverlayProps>> {
+export class GestureOverlay extends ObservableReactComponent<React.PropsWithChildren<GestureOverlayProps>> {
     static Instance: GestureOverlay;
     static Instances: GestureOverlay[] = [];
 
@@ -84,7 +85,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
 
     constructor(props: any) {
         super(props);
-
+        makeObservable(this);
         GestureOverlay.Instances.push(this);
     }
 
@@ -92,9 +93,9 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
         GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
         GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
     }
-    componentDidMount = () => {
+    componentDidMount() {
         GestureOverlay.Instance = this;
-    };
+    }
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
@@ -383,9 +384,9 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
         return GestureOverlay.getBounds(this._points);
     }
 
-    @computed get elements() {
+    get elements() {
         const selView = GestureOverlay.DownDocView;
-        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1);
+        const width = Number(ActiveInkWidth()) * NumCast(selView?.Document._freeform_scale, 1); // * (selView?._props.ScreenToLocalTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
         B.left = B.left - width / 2;
@@ -499,7 +500,7 @@ export class GestureOverlay extends React.Component<React.PropsWithChildren<Gest
 
     render() {
         return (
-            <div className="gestureOverlay-cont" style={{ pointerEvents: this.props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
+            <div className="gestureOverlay-cont" style={{ pointerEvents: this._props.isActive ? 'all' : 'none' }} ref={this._overlayRef} onPointerDown={this.onPointerDown}>
                 {this.showMobileInkOverlay ? <MobileInkOverlay /> : null}
                 {this.elements}
 
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 884c74f9b..69c663a3e 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,5 +1,5 @@
 import { Bezier } from 'bezier-js';
-import { action, observable, reaction } from 'mobx';
+import { action, makeObservable, observable, reaction } from 'mobx';
 import { Doc, NumListCast, Opt } from '../../fields/Doc';
 import { InkData, InkField, InkTool, PointData } from '../../fields/InkField';
 import { List } from '../../fields/List';
@@ -25,6 +25,7 @@ export class InkStrokeProperties {
 
     constructor() {
         InkStrokeProperties._Instance = this;
+        makeObservable(this);
         reaction(
             () => this._controlButton,
             button => button && (Doc.ActiveTool = InkTool.None)
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index de2ce3189..98d1e58e5 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, FieldResult, Opt } from '../../fields/Doc';
@@ -56,6 +56,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         if (LightboxView.Instance) console.log('SDFSFASFASFSALFKJD:SLFJS:LDFJKS:LFJS:LDJFL:SDFJL:SDJF:LSJ');
         LightboxView.Instance = this;
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f65675792..636f8761f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -230,7 +230,7 @@ export class MainView extends ObservableReactComponent<{}> {
         document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener);
     }
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 2e27d1f70..285476b14 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -6,7 +6,7 @@ import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../field
 import { List } from '../../fields/List';
 import { NumCast } from '../../fields/Types';
 import { GetEffectiveAcl } from '../../fields/util';
-import { copyProps, unimplementedFunction, Utils } from '../../Utils';
+import { unimplementedFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { DragManager } from '../util/DragManager';
 import { FollowLinkScript } from '../util/LinkFollower';
diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index 2d8dc9af9..9b2b00903 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -1,8 +1,6 @@
-import { makeObservable, observable } from 'mobx';
+import { action, 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
@@ -10,12 +8,14 @@ export interface AntimodeMenuProps {}
  */
 export abstract class ObservableReactComponent<T> extends React.Component<T, {}> {
     @observable _props: React.PropsWithChildren<T>;
-    constructor(props: React.PropsWithChildren<T>) {
+    constructor(props: any) {
         super(props);
         this._props = props;
         makeObservable(this);
     }
     componentDidUpdate(prevProps: Readonly<T>): void {
-        copyProps(this, prevProps);
+        Object.keys(prevProps).forEach(action(pkey =>
+            (prevProps as any)[pkey] !== (this.props as any)[pkey] &&
+                ((this._props as any)[pkey] = (this.props as any)[pkey]))); // prettier-ignore
     }
 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index c857c2d6b..2fcb5d12a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -54,7 +54,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     private _widthUndo?: UndoManager.Batch;
 
     public static Instance: PropertiesView | undefined;
-    constructor(props: React.PropsWithChildren<PropertiesViewProps>) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         PropertiesView.Instance = this;
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 416162aeb..086f40e96 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../fields/Doc';
@@ -27,6 +27,7 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
 
     constructor(props: ScriptBoxProps) {
         super(props);
+        makeObservable(this);
         this._scriptText = props.initialText || '';
     }
 
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 5f20bc745..acf0ecff4 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -35,13 +35,11 @@ export class ScriptingObjectDisplay extends ObservableReactComponent<ReplProps>
         const val = this._props.value;
         const proto = Object.getPrototypeOf(val);
         const name = (proto && proto.constructor && proto.constructor.name) || String(val);
-        const title = this._props.name ? (
+        const title = (
             <>
-                <b>{this._props.name} : </b>
+                {this.props.name ? <b>{this._props.name} : </b> : <></>}
                 {name}
             </>
-        ) : (
-            name
         );
         if (this.collapsed) {
             return (
@@ -86,31 +84,19 @@ export class ScriptingValueDisplay extends ObservableReactComponent<replValuePro
 
     render() {
         const val = this._props.name ? this._props.value[this._props.name] : this._props.value;
+        const title = (name: string) => (
+            <>
+                {this._props.name ? <b>{this._props.name} : </b> : <> </>}
+                {name}
+            </>
+        );
         if (typeof val === 'object') {
             return <ScriptingObjectDisplay scrollToBottom={this._props.scrollToBottom} value={val} name={this._props.name} />;
         } else if (typeof val === 'function') {
             const name = '[Function]';
-            const title = this._props.name ? (
-                <>
-                    <b>{this._props.name} : </b>
-                    {name}
-                </>
-            ) : (
-                name
-            );
-            return <div className="scriptingObject-leaf">{title}</div>;
-        } else {
-            const name = String(val);
-            const title = this._props.name ? (
-                <>
-                    <b>{this._props.name} : </b>
-                    {name}
-                </>
-            ) : (
-                name
-            );
-            return <div className="scriptingObject-leaf">{title}</div>;
+            return <div className="scriptingObject-leaf">{title('[Function]')}</div>;
         }
+        return <div className="scriptingObject-leaf">{title(String(val))}</div>;
     }
 }
 
@@ -254,7 +240,8 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
         ele && ele.scroll({ behavior: 'auto', top: ele.scrollHeight });
     }
 
-    componentDidUpdate() {
+    componentDidUpdate(prevProps: Readonly<{}>) {
+        super.componentDidUpdate(prevProps);
         if (this.shouldScroll) {
             this.shouldScroll = false;
             this.scrollToBottom();
diff --git a/src/client/views/animationtimeline/Region.tsx b/src/client/views/animationtimeline/Region.tsx
index abbc8203d..b09456cd7 100644
--- a/src/client/views/animationtimeline/Region.tsx
+++ b/src/client/views/animationtimeline/Region.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
@@ -185,6 +185,7 @@ export class Region extends React.Component<IProps> {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
     }
     componentDidMount() {
         setTimeout(() => {
diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx
index 1769c41bd..97a571dc4 100644
--- a/src/client/views/animationtimeline/TimelineMenu.tsx
+++ b/src/client/views/animationtimeline/TimelineMenu.tsx
@@ -1,7 +1,7 @@
 import { IconLookup } from '@fortawesome/fontawesome-svg-core';
 import { faChartLine, faClipboard } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils } from '../../../Utils';
@@ -16,8 +16,9 @@ export class TimelineMenu extends React.Component {
     @observable private _y = 0;
     @observable private _currentMenu: JSX.Element[] = [];
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
+        makeObservable(this);
         TimelineMenu.Instance = this;
     }
 
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0d3ff8bba..97d5cfc70 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -63,10 +63,10 @@ export class CollectionDockingView extends CollectionSubView() {
     private _goldenLayout: any = null;
     static _highlightStyleSheet: any = addStyleSheet();
 
-    constructor(props: SubCollectionViewProps) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
-        if (this._props.renderDepth < 0) runInAction(() => (CollectionDockingView.Instance = this));
+        if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
         //Why is this here?
         (window as any).React = React;
         (window as any).ReactDOM = ReactDOM;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 3ade2ab56..443aa3f17 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,14 +2,14 @@ import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import { Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx';
+import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 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 { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
+import { 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';
@@ -40,10 +40,11 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         this.FieldKey = '';
         CollectionMenu.Instance = this;
         this._canFade = false; // don't let the inking menu fade away
-        runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true)));
+        this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true);
         this.jumpTo(300, 300);
     }
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index d37a942d0..fb8bc4da2 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -54,7 +54,7 @@ export enum TrimScope {
 }
 
 @observer
-export class CollectionStackedTimeline extends CollectionSubView<SubCollectionViewProps & CollectionStackedTimelineProps>() {
+export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
     @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
     @observable public static CurrentlyPlaying: DocumentView[];
     constructor(props: any) {
@@ -695,7 +695,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         this._lastTimecode = this._props.currentTimecode();
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2302bfbc3..f9d575da2 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -58,7 +58,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
     @observable _heading = '';
     @observable _color = '';
 
-    constructor(props: CSVFieldColumnProps) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         this._heading = this._props.headingObject ? this._props.headingObject.heading : this._props.heading;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f1ef52e60..0673b264b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -121,7 +121,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         switch (type) {
             default:
             case CollectionViewType.Freeform:     return <CollectionFreeFormView    key="collview" {...props} />;
-            case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
             case CollectionViewType.Schema:       return <CollectionSchemaView      key="collview" {...props} />;
             case CollectionViewType.Docking:      return <CollectionDockingView     key="collview" {...props} />;
             case CollectionViewType.Tree:         return <CollectionTreeView        key="collview" {...props} />;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 2eb13fd2f..783817b06 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -340,6 +340,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
         runInAction(() => TabDocView._allTabs.add(this));
     }
     componentDidUpdate(prevProps: Readonly<TabDocViewProps>) {
+        super.componentDidUpdate(prevProps);
         this._view && DocumentManager.Instance.AddView(this._view);
     }
 
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 52ecdfbec..a7705ea7e 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -296,6 +296,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
     }
 
     componentDidUpdate(prevProps: Readonly<TreeViewProps>) {
+        super.componentDidUpdate(prevProps);
         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 0d5fcdaeb..66aa29de0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 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.
@@ -83,7 +82,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec
         this.initState();
     }
     componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) {
-        copyProps(this, prevProps);
+        super.componentDidUpdate(prevProps);
         this.clearState();
         this.initState();
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
index 4d3752c02..5637f9aeb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx
@@ -18,23 +18,19 @@ export interface CollectionFreeFormInfoUIProps {
 export class CollectionFreeFormInfoUI extends ObservableReactComponent<CollectionFreeFormInfoUIProps> {
     private _disposers: { [name: string]: IReactionDisposer } = {};
 
-    @observable _currState: infoState | undefined = undefined;
-    get currState() {
-        return this._currState!;
-    }
-    set currState(val) {
-        this._currState = val;
-    }
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
         this.currState = this.setupStates();
     }
 
+    @observable _currState: infoState | undefined = undefined;
+    get currState() { return this._currState!; } // prettier-ignore
+    set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore
+
     setCurrState = (state: infoState) => {
         if (state) {
-            runInAction(() => (this.currState = state));
+            this.currState = state;
             this.currState[StateEntryFunc]?.();
         }
     };
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 7aa68b0d9..79cc534dc 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IconButton } from 'browndash-components';
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { unimplementedFunction } from '../../../../Utils';
@@ -18,8 +18,9 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
     public hideMarquee: () => void = unimplementedFunction;
     public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
     public isShown = () => this._opacity > 0;
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
+        makeObservable(this);
         MarqueeOptionsMenu.Instance = this;
     }
 
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b951a4b17..9132c0436 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -127,7 +127,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.widthSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._xMargin);
+            return this._props.PanelWidth() - (this.totalFixedAllocation + resizerWidth * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._xMargin);
         }
     }
 
@@ -189,7 +189,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0);
+                return this._props.ScreenToLocalTransform().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
             }
             offset += this.lookupPixels(candidate) + resizerWidth;
         }
@@ -223,8 +223,8 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                             if (this.childDocs.includes(d)) {
                                 if (dropInd > this.childDocs.indexOf(d)) dropInd--;
                             }
-                            Doc.RemoveDocFromList(this.dataDoc, this.props.fieldKey, d);
-                            Doc.AddDocToList(this.dataDoc, this.props.fieldKey, d, DocListCast(this.dataDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+                            Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d);
+                            Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
                     })
                 );
@@ -237,56 +237,56 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => {
-        const childDocsActive = this.props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
-        return this.props.isContentActive?.() === false || childDocsActive === false
+        const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
+        return this._props.isContentActive?.() === false || childDocsActive === false
             ? false //
-            : this.props.isDocumentActive?.() && childDocsActive
+            : this._props.isDocumentActive?.() && childDocsActive
               ? true
               : undefined;
     };
     getDisplayDoc = (childLayout: Doc) => {
         const width = () => this.lookupPixels(childLayout);
-        const height = () => this.props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+        const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
         const dxf = () =>
             this.lookupIndividualTransform(childLayout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
-                .scale(this.props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
+                .scale(this._props.NativeDimScaling?.() || 1);
+        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(childLayout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={childLayout}
                 TemplateDataDocument={childLayout.resolvedDataDoc as Doc}
-                styleProvider={this.props.styleProvider}
-                docViewPath={this.props.docViewPath}
-                LayoutTemplate={this.props.childLayoutTemplate}
-                LayoutTemplateString={this.props.childLayoutString}
-                renderDepth={this.props.renderDepth + 1}
+                styleProvider={this._props.styleProvider}
+                docViewPath={this._props.docViewPath}
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
-                dragAction={(this.props.Document.childDragAction ?? this.props.childDragAction) as dropActionType}
+                dragAction={(this._props.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
                 onClick={this.onChildClickHandler}
                 onDoubleClick={this.onChildDoubleClickHandler}
                 suppressSetHeight={true}
                 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}
-                fitContentsToBox={this.props.fitContentsToBox}
-                focus={this.props.focus}
+                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}
+                fitContentsToBox={this._props.fitContentsToBox}
+                focus={this._props.focus}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                dontRegisterView={this.props.dontRegisterView}
-                addDocument={this.props.addDocument}
-                moveDocument={this.props.moveDocument}
-                removeDocument={this.props.removeDocument}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
+                dontRegisterView={this._props.dontRegisterView}
+                addDocument={this._props.addDocument}
+                moveDocument={this._props.moveDocument}
+                removeDocument={this._props.removeDocument}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                addDocTab={this._props.addDocTab}
+                pinToPres={this._props.pinToPres}
                 bringToFront={returnFalse}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
@@ -306,16 +306,16 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 <Tooltip title={'Tab: ' + StrCast(layout.title)}>
                     <div className="document-wrapper" key={'wrapper' + i} style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
                         {this.getDisplayDoc(layout)}
-                        <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this.props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+                        <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
                         <WidthLabel layout={layout} collectionDoc={this.Document} />
                     </div>
                 </Tooltip>,
                 <ResizeBar
                     width={resizerWidth}
                     key={'resizer' + i}
-                    styleProvider={this.props.styleProvider}
-                    isContentActive={this.props.isContentActive}
-                    select={this.props.select}
+                    styleProvider={this._props.styleProvider}
+                    isContentActive={this._props.isContentActive}
+                    select={this._props.select}
                     columnUnitLength={this.getColumnUnitLength}
                     toLeft={layout}
                     toRight={childLayoutPairs[i + 1]?.layout}
@@ -332,12 +332,12 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 className={'collectionMulticolumnView_contents'}
                 ref={this.createDashEventsTarget}
                 style={{
-                    width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
-                    height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
-                    marginLeft: NumCast(this.props.Document._xMargin),
-                    marginRight: NumCast(this.props.Document._xMargin),
-                    marginTop: NumCast(this.props.Document._yMargin),
-                    marginBottom: NumCast(this.props.Document._yMargin),
+                    width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`,
+                    height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`,
+                    marginLeft: NumCast(this.Document._xMargin),
+                    marginRight: NumCast(this.Document._xMargin),
+                    marginTop: NumCast(this.Document._yMargin),
+                    marginBottom: NumCast(this.Document._yMargin),
                 }}>
                 {this.contents}
             </div>
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index f1c6c6e1e..cf65bf12a 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -123,7 +123,7 @@ export class CollectionMultirowView extends CollectionSubView() {
     private get totalRatioAllocation(): number | undefined {
         const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
         if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
-            return this.props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this.props.Document._yMargin);
+            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin);
         }
     }
 
@@ -185,7 +185,7 @@ export class CollectionMultirowView extends CollectionSubView() {
         let offset = 0;
         for (const { layout: candidate } of this.childLayoutPairs) {
             if (candidate === layout) {
-                return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1));
+                return this._props.ScreenToLocalTransform().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
             }
             offset += this.lookupPixels(candidate) + resizerHeight;
         }
@@ -219,8 +219,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                             if (this.childDocs.includes(d)) {
                                 if (dropInd > this.childDocs.indexOf(d)) dropInd--;
                             }
-                            Doc.RemoveDocFromList(this.dataDoc, this.props.fieldKey, d);
-                            Doc.AddDocToList(this.dataDoc, this.props.fieldKey, d, DocListCast(this.dataDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1);
+                            Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d);
+                            Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1);
                         }
                     })
                 );
@@ -233,32 +233,32 @@ export class CollectionMultirowView extends CollectionSubView() {
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => {
-        const childDocsActive = this.props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
-        return this.props.isContentActive?.() === false || childDocsActive === false
+        const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
+        return this._props.isContentActive?.() === false || childDocsActive === false
             ? false //
-            : this.props.isDocumentActive?.() && childDocsActive
+            : this._props.isDocumentActive?.() && childDocsActive
               ? true
               : undefined;
     };
     getDisplayDoc = (layout: Doc) => {
         const height = () => this.lookupPixels(layout);
-        const width = () => this.props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
+        const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
         const dxf = () =>
             this.lookupIndividualTransform(layout)
                 .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
-                .scale(this.props.NativeDimScaling?.() || 1);
-        const shouldNotScale = () => this.props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
+                .scale(this._props.NativeDimScaling?.() || 1);
+        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
         return (
             <DocumentView
                 Document={layout}
                 TemplateDataDocument={layout.resolvedDataDoc as Doc}
-                styleProvider={this.props.styleProvider}
-                docViewPath={this.props.docViewPath}
-                LayoutTemplate={this.props.childLayoutTemplate}
-                LayoutTemplateString={this.props.childLayoutString}
-                renderDepth={this.props.renderDepth + 1}
+                styleProvider={this._props.styleProvider}
+                docViewPath={this._props.docViewPath}
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                renderDepth={this._props.renderDepth + 1}
                 PanelWidth={width}
                 PanelHeight={height}
                 rootSelected={this.rootSelected}
@@ -267,22 +267,22 @@ export class CollectionMultirowView extends CollectionSubView() {
                 onDoubleClick={this.onChildDoubleClickHandler}
                 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}
-                fitContentsToBox={this.props.fitContentsToBox}
-                dragAction={this.props.childDragAction}
-                focus={this.props.focus}
+                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}
+                fitContentsToBox={this._props.fitContentsToBox}
+                dragAction={this._props.childDragAction}
+                focus={this._props.focus}
                 childFilters={this.childDocFilters}
                 childFiltersByRanges={this.childDocRangeFilters}
                 searchFilterDocs={this.searchFilterDocs}
-                dontRegisterView={this.props.dontRegisterView}
-                addDocument={this.props.addDocument}
-                moveDocument={this.props.moveDocument}
-                removeDocument={this.props.removeDocument}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.pinToPres}
+                dontRegisterView={this._props.dontRegisterView}
+                addDocument={this._props.addDocument}
+                moveDocument={this._props.moveDocument}
+                removeDocument={this._props.removeDocument}
+                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
+                addDocTab={this._props.addDocTab}
+                pinToPres={this._props.pinToPres}
                 bringToFront={returnFalse}
                 dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
             />
@@ -305,8 +305,8 @@ export class CollectionMultirowView extends CollectionSubView() {
                 </div>,
                 <ResizeBar
                     height={resizerHeight}
-                    styleProvider={this.props.styleProvider}
-                    isContentActive={this.props.isContentActive}
+                    styleProvider={this._props.styleProvider}
+                    isContentActive={this._props.isContentActive}
                     key={'resizer' + i}
                     columnUnitLength={this.getRowUnitLength}
                     toTop={layout}
@@ -323,12 +323,12 @@ export class CollectionMultirowView extends CollectionSubView() {
             <div
                 className={'collectionMultirowView_contents'}
                 style={{
-                    width: `calc(100% - ${2 * NumCast(this.props.Document._xMargin)}px)`,
-                    height: `calc(100% - ${2 * NumCast(this.props.Document._yMargin)}px)`,
-                    marginLeft: NumCast(this.props.Document._xMargin),
-                    marginRight: NumCast(this.props.Document._xMargin),
-                    marginTop: NumCast(this.props.Document._yMargin),
-                    marginBottom: NumCast(this.props.Document._yMargin),
+                    width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`,
+                    height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`,
+                    marginLeft: NumCast(this.Document._xMargin),
+                    marginRight: NumCast(this.Document._xMargin),
+                    marginTop: NumCast(this.Document._yMargin),
+                    marginBottom: NumCast(this.Document._yMargin),
                 }}
                 ref={this.createDashEventsTarget}>
                 {this.contents}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c97c879af..548734dab 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -83,7 +83,8 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
     PanelWidth = ()        => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
     PanelHeight = ()       => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
 
-    componentDidUpdate() {
+    componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
+        super.componentDidUpdate(prevProps);
         this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
     }
     render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 343f770d5..ab413e6f2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1334,7 +1334,7 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
         return this._selected;
     }
     public set SELECTED(val) {
-        this._selected = val;
+        runInAction(() => (this._selected = val));
     }
     @observable public static Interacting = false;
     @observable public static LongPress = false;
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index 29bffb583..c26579e66 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -24,7 +24,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
     _plotId = '';
     _plotEle: any;
 
-    constructor(props: React.PropsWithChildren<FieldViewProps>) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         this._plotId = 'graph' + FunctionPlotBox.GraphCount++;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index b1ccd38ba..876f13370 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -55,8 +55,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     @observable _curSuffix = '';
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(props);
+        makeObservable(this);
         this._props.setContentView?.(this);
     }
 
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index fa7a55bc7..d0a9f10b4 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
 import wiki from 'wikijs';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types';
-import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -82,7 +82,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
         this.updateHref();
     }
     componentDidUpdate(prevProps: Readonly<LinkDocPreviewProps>) {
-        copyProps(this, prevProps);
+        super.componentDidUpdate(prevProps);
         if (prevProps.linkSrc !== this._props.linkSrc || prevProps.linkDoc !== this._props.linkDoc || prevProps.hrefs !== this._props.hrefs) this.init();
     }
     componentDidMount() {
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index b458e5a28..66c4dc7b8 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IReactionDisposer, ObservableMap, reaction } from 'mobx';
+import { IReactionDisposer, ObservableMap, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction } from '../../../../Utils';
@@ -34,8 +34,9 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this._left > 0;
     }
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
+        makeObservable(this);
         MapAnchorMenu.Instance = this;
         MapAnchorMenu.Instance._canFade = false;
     }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 213f88177..733febd2d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -58,7 +58,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
     }
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
index c498a58d6..e75b1ab6f 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx
@@ -202,7 +202,8 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP
         ];
     }
 
-    componentDidUpdate() {
+    componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<FieldViewProps>>) {
+        super.componentDidUpdate(prevProps);
         if (this.xMax !== this.props.PanelWidth() * 0.6 || this.yMax != this.props.PanelHeight()) {
             this.xMax = this.props.PanelWidth() * 0.6;
             this.yMax = this.props.PanelHeight();
diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
index 99333991f..f5077a07e 100644
--- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
+++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx
@@ -1,4 +1,4 @@
-import { computed, IReactionDisposer, reaction } from 'mobx';
+import { computed, IReactionDisposer, makeObservable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import './PhysicsSimulationBox.scss';
 import * as React from 'react';
@@ -93,6 +93,7 @@ interface IState {
 export default class Weight extends React.Component<IWeightProps, IState> {
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         this.state = {
             angleLabel: 0,
             clickPositionX: 0,
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
index 191877cb5..061a46f03 100644
--- a/src/client/views/nodes/RadialMenu.tsx
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import './RadialMenu.scss';
 import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem';
@@ -9,9 +9,9 @@ export class RadialMenu extends React.Component {
     static Instance: RadialMenu;
     static readonly buffer = 20;
 
-    constructor(props: Readonly<{}>) {
+    constructor(props: any) {
         super(props);
-
+        makeObservable(this);
         RadialMenu.Instance = this;
     }
 
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index a7ff8ff8f..d5d31b407 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 // import { Canvas } from '@react-three/fiber';
-import { computed, observable, runInAction } from 'mobx';
+import { computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three';
 import { DateField } from '../../../fields/DateField';
@@ -123,14 +123,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
 
     constructor(props: any) {
         super(props);
-        if (this.dataDoc.videoWall) {
-            this.layoutDoc.nativeWidth = undefined;
-            this.layoutDoc.nativeHeight = undefined;
-            this.layoutDoc.popOff = 0;
-            this.layoutDoc.popOut = 1;
-        } else {
-            this.setupDictation();
-        }
+        makeObservable(this);
+        this.setupDictation();
     }
     getAnchor = (addAsAnnotation: boolean) => {
         const startTime = Cast(this.layoutDoc._layout_currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
@@ -173,7 +167,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
     };
 
     @computed get content() {
-        if (this.layoutDoc.videoWall) return null;
         return (
             <video
                 className={'videoBox-content'}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 127edaed7..7e7eaee45 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -1,5 +1,5 @@
 let ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default;
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
@@ -60,6 +60,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         if (!this.compileParams.length) {
             const params = ScriptCast(this.dataDoc[this.props.fieldKey])?.script.options.params as { [key: string]: any };
             if (params) {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ebe86318d..df73dffe4 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -68,7 +68,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     private _playRegionTimer: any = null; // timeout for playback
     private _controlsFadeTimer: any = null; // timeout for controls fade
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         this._props.setContentView?.(this);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2f92f1edb..57045e2af 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -98,7 +98,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
 
-    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
+    constructor(props: any) {
         super(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.
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index d6b053c8e..b7d2a24c2 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -54,7 +54,7 @@ interface IDashDocCommentViewInternal {
 }
 
 export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
-    constructor(props: IDashDocCommentViewInternal) {
+    constructor(props: any) {
         super(props);
         this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
         this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this);
diff --git a/src/client/views/nodes/formattedText/EquationEditor.tsx b/src/client/views/nodes/formattedText/EquationEditor.tsx
index 07c70af77..b4102e08e 100644
--- a/src/client/views/nodes/formattedText/EquationEditor.tsx
+++ b/src/client/views/nodes/formattedText/EquationEditor.tsx
@@ -44,7 +44,7 @@ class EquationEditor extends Component<EquationEditorProps> {
     // Element needs to be in the class format and thus requires a constructor. The steps that are run
     // in the constructor is to make sure that React can succesfully communicate with the equation
     // editor.
-    constructor(props: EquationEditorProps) {
+    constructor(props: any) {
         super(props);
 
         this.element = createRef();
diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx
index 5ee5d25c3..331ed1980 100644
--- a/src/client/views/nodes/formattedText/EquationView.tsx
+++ b/src/client/views/nodes/formattedText/EquationView.tsx
@@ -8,6 +8,7 @@ import { StrCast } from '../../../../fields/Types';
 import './DashFieldView.scss';
 import { FormattedTextBox } from './FormattedTextBox';
 import * as React from 'react';
+import { AnyArray } from 'mongoose';
 
 export class EquationView {
     dom: HTMLDivElement; // container for label and value
@@ -63,7 +64,7 @@ export class EquationViewInternal extends React.Component<IEquationViewInternal>
     _fieldKey: string;
     _ref: React.RefObject<EquationEditor> = React.createRef();
 
-    constructor(props: IEquationViewInternal) {
+    constructor(props: any) {
         super(props);
         this._fieldKey = this.props.fieldKey;
         this._textBoxDoc = this.props.tbox.props.Document;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 997c3f86d..ad2fab8b0 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -208,7 +208,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
     }
 
-    constructor(props: React.PropsWithChildren<FieldViewProps & FormattedTextBoxProps>) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         FormattedTextBox.Instance = this;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 4ff816812..17993d88e 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation } from '../../../../fields/DocSymbols';
@@ -72,6 +72,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         if (!PresBox.navigateToDocScript) {
             PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
         }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index a8c83ded6..d0688c338 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -23,7 +23,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     private _commentRef = React.createRef<HTMLDivElement>();
     private _cropRef = React.createRef<HTMLDivElement>();
 
-    constructor(props: AntimodeMenuProps) {
+    constructor(props: any) {
         super(props);
         makeObservable(this);
         AnchorMenu.Instance = this;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 0cda36509..cdd0cb95a 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import './GPTPopup.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
@@ -14,6 +14,7 @@ import { AnchorMenu } from '../AnchorMenu';
 import { gptImageCall } from '../../../apis/gpt/GPT';
 import { Networking } from '../../../Network';
 import { Utils } from '../../../../Utils';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 
 export enum GPTPopupMode {
     SUMMARY,
@@ -24,7 +25,7 @@ export enum GPTPopupMode {
 interface GPTPopupProps {}
 
 @observer
-export class GPTPopup extends React.Component<GPTPopupProps> {
+export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     static Instance: GPTPopup;
 
     @observable
@@ -177,6 +178,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
 
     constructor(props: GPTPopupProps) {
         super(props);
+        makeObservable(this);
         GPTPopup.Instance = this;
     }
 
diff --git a/src/client/views/search/CheckBox.scss b/src/client/views/search/CheckBox.scss
deleted file mode 100644
index 4892facbc..000000000
--- a/src/client/views/search/CheckBox.scss
+++ /dev/null
@@ -1,58 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.checkboxfilter {
-    display: flex;
-    margin-top: 0px;
-    padding: 3px;
-
-    .outer {
-        display: flex;
-        position: relative;
-        justify-content: center;
-        align-items: center;
-        margin-top: 0px;
-
-        .check-container:hover ~ .check-box {
-            background-color: $medium-blue;
-        }
-
-        .check-container {
-            width: 40px;
-            height: 40px;
-            position: absolute;
-            z-index: 1000;
-
-            .checkmark {
-                z-index: 1000;
-                position: absolute;
-                fill-opacity: 0;
-                stroke-width: 4px;
-                // stroke: white;
-                stroke: gray;
-            }
-        }
-
-        .check-box {
-            z-index: 900;
-            position: relative;
-            height: 20px;
-            width: 20px;
-            overflow: visible;
-            background-color: transparent;
-            border-style: solid;
-            border-color: $medium-gray;
-            border-width: 2px;
-            -webkit-transition: all 0.2s ease-in-out;
-            -moz-transition: all 0.2s ease-in-out;
-            -o-transition: all 0.2s ease-in-out;
-            transition: all 0.2s ease-in-out;
-        }
-    }
-
-    .checkbox-title {
-        display: flex;
-        align-items: center;
-        text-transform: capitalize;
-        margin-left: 15px;
-    }
-}
diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx
deleted file mode 100644
index 0a1e551ec..000000000
--- a/src/client/views/search/CheckBox.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import "./CheckBox.scss";
-
-interface CheckBoxProps {
-    originalStatus: boolean;
-    updateStatus(newStatus: boolean): void;
-    title: string;
-    parent: any;
-    numCount: number;
-    default: boolean;
-}
-
-@observer
-export class CheckBox extends React.Component<CheckBoxProps>{
-    // true = checked, false = unchecked
-    @observable private _status: boolean;
-    // @observable private uncheckTimeline: anime.AnimeTimelineInstance;
-    // @observable private checkTimeline: anime.AnimeTimelineInstance;
-    @observable private checkRef: any;
-    @observable private _resetReaction?: IReactionDisposer;
-
-
-    constructor(props: CheckBoxProps) {
-        super(props);
-        this._status = this.props.originalStatus;
-        this.checkRef = React.createRef();
-
-        // this.checkTimeline = anime.timeline({
-        //     loop: false,
-        //     autoplay: false,
-        //     direction: "normal",
-        // }); this.uncheckTimeline = anime.timeline({
-        //     loop: false,
-        //     autoplay: false,
-        //     direction: "normal",
-        // });
-    }
-
-    // componentDidMount = () => {
-    //     this.uncheckTimeline.add({
-    //         targets: this.checkRef.current,
-    //         easing: "easeInOutQuad",
-    //         duration: 500,
-    //         opacity: 0,
-    //     });
-    //     this.checkTimeline.add({
-    //         targets: this.checkRef.current,
-    //         easing: "easeInOutQuad",
-    //         duration: 500,
-    //         strokeDashoffset: [anime.setDashoffset, 0],
-    //         opacity: 1
-    //     });
-
-    //     if (this.props.originalStatus) {
-    //         this.checkTimeline.play();
-    //         this.checkTimeline.restart();
-    //     }
-
-    //     this._resetReaction = reaction(
-    //         () => this.props.parent._resetBoolean,
-    //         () => {
-    //             if (this.props.parent._resetBoolean) {
-    //                 runInAction(() => {
-    //                     this.reset();
-    //                     this.props.parent._resetCounter++;
-    //                     if (this.props.parent._resetCounter === this.props.numCount) {
-    //                         this.props.parent._resetCounter = 0;
-    //                         this.props.parent._resetBoolean = false;
-    //                     }
-    //                 });
-    //             }
-    //         },
-    //     );
-    // }
-
-    // @action.bound
-    // reset() {
-    //     if (this.props.default) {
-    //         if (!this._status) {
-    //             this._status = true;
-    //             this.checkTimeline.play();
-    //             this.checkTimeline.restart();
-    //         }
-    //     }
-    //     else {
-    //         if (this._status) {
-    //             this._status = false;
-    //             this.uncheckTimeline.play();
-    //             this.uncheckTimeline.restart();
-    //         }
-    //     }
-
-    //     this.props.updateStatus(this.props.default);
-    // }
-
-    @action.bound
-    onClick = () => {
-        // if (this._status) {
-        //     this.uncheckTimeline.play();
-        //     this.uncheckTimeline.restart();
-        // }
-        // else {
-        //     this.checkTimeline.play();
-        //     this.checkTimeline.restart();
-
-        // }
-        // this._status = !this._status;
-        // this.props.updateStatus(this._status);
-
-    }
-
-    render() {
-        return (
-            <div className="checkboxfilter" onClick={this.onClick}>
-                <div className="outer">
-                    <div className="check-container">
-                        <svg viewBox="0 12 40 40">
-                            <path ref={this.checkRef} className="checkmark" d="M14.1 27.2l7.1 7.2 16.7-18" />
-                        </svg>
-                    </div>
-                    <div className="check-box" />
-                </div>
-                <div className="checkbox-title">{this.props.title}</div>
-            </div>
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/search/CollectionFilters.scss b/src/client/views/search/CollectionFilters.scss
deleted file mode 100644
index 6105df570..000000000
--- a/src/client/views/search/CollectionFilters.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.collection-filters {
-    display: flex;
-    flex-direction: row;
-    width: 100%;
-
-    &.main {
-        width: 47%;
-        float: left;
-    }
-
-    &.optional {
-        width: 60%;
-        display: grid;
-        grid-template-columns: 50% 50%;
-        float: right;
-        opacity: 0;
-    }
-}
diff --git a/src/client/views/search/CollectionFilters.tsx b/src/client/views/search/CollectionFilters.tsx
deleted file mode 100644
index 48d0b2ddb..000000000
--- a/src/client/views/search/CollectionFilters.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as React from 'react';
-import { observable, action } from 'mobx';
-import { CheckBox } from './CheckBox';
-import "./CollectionFilters.scss";
-import * as anime from 'animejs';
-
-interface CollectionFilterProps {
-    collectionStatus: boolean;
-    updateCollectionStatus(val: boolean): void;
-    collectionSelfStatus: boolean;
-    updateSelfCollectionStatus(val: boolean): void;
-    collectionParentStatus: boolean;
-    updateParentCollectionStatus(val: boolean): void;
-}
-
-export class CollectionFilters extends React.Component<CollectionFilterProps> {
-
-    static Instance: CollectionFilters;
-
-    @observable public _resetBoolean = false;
-    @observable public _resetCounter: number = 0;
-    
-    @observable private _collectionsSelected = this.props.collectionStatus;
-    @observable private _timeline: anime.AnimeTimelineInstance;
-    @observable private _ref: any;
-
-    constructor(props: CollectionFilterProps) {
-        super(props);
-        CollectionFilters.Instance = this;
-        this._ref = React.createRef();
-
-        this._timeline = anime.timeline({
-            loop: false,
-            autoplay: false,
-            direction: "reverse",
-        });
-    }
-
-    componentDidMount = () => {
-        this._timeline.add({
-            targets: this._ref.current,
-            easing: "easeInOutQuad",
-            duration: 500,
-            opacity: 1,
-        });
-
-        if (this._collectionsSelected) {
-            this._timeline.play();
-            this._timeline.reverse();
-        }
-    }
-
-    @action.bound
-    resetCollectionFilters() { this._resetBoolean = true; }
-
-    @action.bound
-    updateColStat(val: boolean) {
-        this.props.updateCollectionStatus(val);
-
-        if (this._collectionsSelected !== val) {
-            this._timeline.play();
-            this._timeline.reverse();
-        }
-
-        this._collectionsSelected = val;
-    }
-
-    render() {
-        return (
-            <div>
-                <div className="collection-filters">
-                    <div className="collection-filters main">
-                        <CheckBox default={false} title={"limit to current collection"} parent={this} numCount={3} updateStatus={this.updateColStat} originalStatus={this.props.collectionStatus} />
-                    </div>
-                    <div className="collection-filters optional" ref={this._ref}>
-                        <CheckBox default={true} title={"Search in self"} parent={this} numCount={3} updateStatus={this.props.updateSelfCollectionStatus} originalStatus={this.props.collectionSelfStatus} />
-                        <CheckBox default={true} title={"Search in parent"} parent={this} numCount={3} updateStatus={this.props.updateParentCollectionStatus} originalStatus={this.props.collectionParentStatus} />
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
deleted file mode 100644
index 05aa099f7..000000000
--- a/src/client/views/search/IconBar.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.icon-bar {
-    display: flex;
-    flex-wrap: wrap;
-    justify-content: space-evenly;
-    height: auto;
-    width: 100%;
-    flex-direction: row-reverse;
-}
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
deleted file mode 100644
index 540c1b5e1..000000000
--- a/src/client/views/search/IconBar.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { action, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
-import './IconBar.scss';
-import { IconButton } from './IconButton';
-import './IconButton.scss';
-
-export interface IconBarProps {
-    setIcons: (icons: string[]) => void;
-}
-
-@observer
-export class IconBar extends React.Component<IconBarProps> {
-    public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB, DocumentType.MAP];
-
-    @observable private _icons: string[] = this._allIcons;
-
-    static Instance: IconBar;
-
-    @observable public _resetClicked: boolean = false;
-    @observable public _selectAllClicked: boolean = false;
-    @observable public _reset: number = 0;
-    @observable public _select: number = 0;
-
-    @action.bound
-    updateIcon(newArray: string[]) {
-        this._icons = newArray;
-        this.props.setIcons?.(this._icons);
-    }
-
-    @action.bound
-    getIcons(): string[] {
-        return this._icons;
-    }
-
-    constructor(props: any) {
-        super(props);
-        IconBar.Instance = this;
-    }
-
-    @action.bound
-    getList(): string[] {
-        return this.getIcons();
-    }
-
-    @action.bound
-    updateList(newList: string[]) {
-        this.updateIcon(newList);
-    }
-
-    @action.bound
-    resetSelf = () => {
-        this._resetClicked = true;
-        this.updateList([]);
-    };
-
-    @action.bound
-    selectAll = () => {
-        this._selectAllClicked = true;
-        this.updateList(this._allIcons);
-    };
-
-    render() {
-        return (
-            <div className="icon-bar">
-                {this._allIcons.map((type: string) => (
-                    <IconButton key={type.toString()} type={type} />
-                ))}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss
deleted file mode 100644
index d2a2ea369..000000000
--- a/src/client/views/search/IconButton.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.type-outer {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    width: 30px;
-
-    .type-icon {
-        height: 30px;
-        width: 30px;
-        color: $white;
-        // background-color: rgb(194, 194, 197);
-        background-color: gray;
-        border-radius: 50%;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        -webkit-transition: all 0.2s ease-in-out;
-        -moz-transition: all 0.2s ease-in-out;
-        -o-transition: all 0.2s ease-in-out;
-        transition: all 0.2s ease-in-out;
-        font-size: 2em;
-
-        .fontawesome-icon {
-            height: 15px;
-            width: 15px;
-        }
-    }
-
-    .filter-description {
-        text-transform: capitalize;
-        font-size: 10;
-        text-align: center;
-        height: 15px;
-        margin-top: 5px;
-        opacity: 1;
-        -webkit-transition: all 0.2s ease-in-out;
-        -moz-transition: all 0.2s ease-in-out;
-        -o-transition: all 0.2s ease-in-out;
-        transition: all 0.2s ease-in-out;
-    }
-
-    .type-icon:hover {
-        transform: scale(1.1);
-        background-color: $medium-blue;
-        opacity: 1;
-
-        + .filter-description {
-            opacity: 1;
-        }
-    }
-}
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
deleted file mode 100644
index 20084b64d..000000000
--- a/src/client/views/search/IconButton.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import * as _ from 'lodash';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { DocumentType } from '../../documents/DocumentTypes';
-import '../global/globalCssVariables.module.scss';
-import { IconBar } from './IconBar';
-import './IconButton.scss';
-import './SearchBox.scss';
-
-interface iconButtonProps {
-    type: string;
-}
-
-@observer
-export class IconButton extends React.Component<iconButtonProps> {
-    @observable private _isSelected: boolean = IconBar.Instance.getIcons().indexOf(this.props.type) !== -1;
-    @observable private _hover = false;
-    private _resetReaction?: IReactionDisposer;
-    private _selectAllReaction?: IReactionDisposer;
-
-    static Instance: IconButton;
-    constructor(props: iconButtonProps) {
-        super(props);
-        IconButton.Instance = this;
-    }
-
-    componentDidMount = () => {
-        this._resetReaction = reaction(
-            () => IconBar.Instance._resetClicked,
-            action(() => {
-                if (IconBar.Instance._resetClicked) {
-                    this._isSelected = false;
-                    IconBar.Instance._reset++;
-                    if (IconBar.Instance._reset === 9) {
-                        IconBar.Instance._reset = 0;
-                        IconBar.Instance._resetClicked = false;
-                    }
-                }
-            })
-        );
-
-        this._selectAllReaction = reaction(
-            () => IconBar.Instance._selectAllClicked,
-            action(() => {
-                if (IconBar.Instance._selectAllClicked) {
-                    this._isSelected = true;
-                    IconBar.Instance._select++;
-                    if (IconBar.Instance._select === 9) {
-                        IconBar.Instance._select = 0;
-                        IconBar.Instance._selectAllClicked = false;
-                    }
-                }
-            })
-        );
-    };
-
-    @action.bound
-    getIcon() {
-        switch (this.props.type) {
-            case DocumentType.NONE:  return 'ban';
-            case DocumentType.AUDIO: return 'music';
-            case DocumentType.COL:   return 'object-group';
-            case DocumentType.IMG:   return 'image';
-            case DocumentType.LINK:  return 'link';
-            case DocumentType.PDF:   return 'file-pdf';
-            case DocumentType.RTF:   return 'sticky-note';
-            case DocumentType.VID:   return 'video';
-            case DocumentType.WEB:   return 'globe-asia';
-            case DocumentType.MAP:   return 'map-marker-alt';
-            default:                 return 'caret-down';
-        } // prettier-ignore
-    }
-
-    @action.bound
-    onClick = () => {
-        const newList: string[] = IconBar.Instance.getIcons();
-
-        if (!this._isSelected) {
-            this._isSelected = true;
-            newList.push(this.props.type);
-        } else {
-            this._isSelected = false;
-            _.pull(newList, this.props.type);
-        }
-
-        IconBar.Instance.updateIcon(newList);
-    };
-
-    selected = {
-        opacity: 1,
-        backgroundColor: '#121721',
-        //backgroundColor: "rgb(128, 128, 128)"
-    };
-
-    notSelected = {
-        opacity: 0.2,
-        backgroundColor: '#121721',
-    };
-
-    hoverStyle = {
-        opacity: 1,
-        backgroundColor: 'rgb(128, 128, 128)',
-        //backgroundColor: "rgb(178, 206, 248)" //$medium-blue
-    };
-
-    render() {
-        return (
-            <div className="type-outer" id={this.props.type + '-filter'} onMouseEnter={() => (this._hover = true)} onMouseLeave={() => (this._hover = false)} onClick={this.onClick}>
-                <div className="type-icon" id={this.props.type + '-icon'} style={this._hover ? this.hoverStyle : this._isSelected ? this.selected : this.notSelected}>
-                    <FontAwesomeIcon className="fontawesome-icon" icon={this.getIcon()} />
-                </div>
-                {/* <div className="filter-description">{this.props.type}</div> */}
-            </div>
-        );
-    }
-}
diff --git a/src/client/views/search/NaviconButton.scss b/src/client/views/search/NaviconButton.scss
deleted file mode 100644
index 917635c0b..000000000
--- a/src/client/views/search/NaviconButton.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-$height-icon: 15px;
-$width-line: 30px;
-$height-line: 4px;
-
-$transition-time: 0.4s;
-$rotation: 45deg;
-$translateY: calc($height-icon / 2);
-$translateX: 0;
-
-#hamburger-icon {
-    width: $width-line;
-    height: $height-icon;
-    position: relative;
-    display: block;
-    transition: all $transition-time;
-    -webkit-transition: all $transition-time;
-    -moz-transition: all $transition-time;
-
-    .line {
-        display: block;
-        background: $medium-gray;
-        width: $width-line;
-        height: $height-line;
-        position: absolute;
-        left: 0;
-        border-radius: calc($height-line / 2);
-        transition: all $transition-time;
-        -webkit-transition: all $transition-time;
-        -moz-transition: all $transition-time;
-
-        &.line-1 {
-            top: 0;
-        }
-
-        &.line-2 {
-            top: 50%;
-        }
-
-        &.line-3 {
-            top: 100%;
-        }
-    }
-}
-
-.filter-header.active {
-    .line-1 {
-        transform: translateY($translateY) translateX($translateX) rotate($rotation);
-        -webkit-transform: translateY($translateY) translateX($translateX) rotate($rotation);
-        -moz-transform: translateY($translateY) translateX($translateX) rotate($rotation);
-    }
-
-    .line-2 {
-        opacity: 0;
-    }
-
-    .line-3 {
-        transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
-        -webkit-transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
-        -moz-transform: translateY($translateY * -1) translateX($translateX) rotate($rotation * -1);
-    }
-}
-
-.filter-header:hover #hamburger-icon {
-    transform: scale(1.1);
-    -webkit-transform: scale(1.1);
-    -moz-transform: scale(1.1);
-}
diff --git a/src/client/views/search/NaviconButton.tsx b/src/client/views/search/NaviconButton.tsx
deleted file mode 100644
index 0fa4a0fca..000000000
--- a/src/client/views/search/NaviconButton.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import "./NaviconButton.scss";
-import * as $ from 'jquery';
-import { observable } from 'mobx';
-
-export interface NaviconProps {
-    onClick(): void;
-}
-
-export class NaviconButton extends React.Component<NaviconProps> {
-
-    @observable private _ref: React.RefObject<HTMLAnchorElement> = React.createRef();
-
-    componentDidMount = () => {
-        const that = this;
-        if (this._ref.current) {
-            this._ref.current.addEventListener("click", function (e) {
-                e.preventDefault();
-                if (that._ref.current) {
-                    that._ref.current.classList.toggle('active');
-                    return false;
-                }
-            });
-        }
-    }
-
-    render() {
-        return (
-            <a id="hamburger-icon" href="#" ref={this._ref} title="Menu">
-                <span className="line line-1"></span>
-                <span className="line line-2"></span>
-                <span className="line line-3"></span>
-            </a>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index a3c4bd2ed..09e459f7d 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -1,5 +1,4 @@
 @import '../global/globalCssVariables.module.scss';
-@import './NaviconButton.scss';
 
 .searchBox-container {
     width: 100%;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3172ff9c0..ccfccb771 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,5 +1,5 @@
 import { Tooltip } from '@mui/material';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../../fields/Doc';
@@ -61,6 +61,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
      */
     constructor(props: any) {
         super(props);
+        makeObservable(this);
         SearchBox.Instance = this;
     }
 
diff --git a/src/client/views/search/ToggleBar.scss b/src/client/views/search/ToggleBar.scss
deleted file mode 100644
index 9e27db2bc..000000000
--- a/src/client/views/search/ToggleBar.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-@import '../global/globalCssVariables.module.scss';
-
-.toggle-title {
-    display: flex;
-    align-items: center;
-    color: $white;
-    text-transform: uppercase;
-    flex-direction: row;
-    justify-content: space-around;
-    padding: 5px;
-
-    .toggle-option {
-        width: 50%;
-        text-align: center;
-        -webkit-transition: all 0.2s ease-in-out;
-        -moz-transition: all 0.2s ease-in-out;
-        -o-transition: all 0.2s ease-in-out;
-        transition: all 0.2s ease-in-out;
-        color: gray;
-        font-size: 13;
-    }
-}
-
-.toggle-bar {
-    // height: 50px;
-    height: 30px;
-    width: 100px;
-    background-color: $medium-gray;
-    border-radius: 10px;
-    padding: 5px;
-    display: flex;
-    align-items: center;
-
-    .toggle-button {
-        // width: 275px;
-        width: 40px;
-        height: 100%;
-        border-radius: 10px;
-        background-color: $white;
-    }
-}
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
deleted file mode 100644
index 466822eba..000000000
--- a/src/client/views/search/ToggleBar.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, computed } from 'mobx';
-import "./SearchBox.scss";
-import "./ToggleBar.scss";
-import * as anime from 'animejs';
-
-export interface ToggleBarProps {
-    originalStatus: boolean;
-    optionOne: string;
-    optionTwo: string;
-    handleChange(): void;
-    getStatus(): boolean;
-}
-
-@observer
-export class ToggleBar extends React.Component<ToggleBarProps>{
-    static Instance: ToggleBar;
-
-    @observable private _forwardTimeline: anime.AnimeTimelineInstance;
-    @observable private _toggleButton: React.RefObject<HTMLDivElement>;
-    @observable private _originalStatus: boolean = this.props.originalStatus;
-
-    constructor(props: ToggleBarProps) {
-        super(props);
-        ToggleBar.Instance = this;
-        this._toggleButton = React.createRef();
-        this._forwardTimeline = anime.timeline({
-            loop: false,
-            autoplay: false,
-            direction: "reverse",
-        });
-    }
-
-    componentDidMount = () => {
-        const totalWidth = 265;
-
-        if (this._originalStatus) {
-            this._forwardTimeline.add({
-                targets: this._toggleButton.current,
-                translateX: totalWidth,
-                easing: "easeInOutQuad",
-                duration: 500
-            });
-        }
-        else {
-            this._forwardTimeline.add({
-                targets: this._toggleButton.current,
-                translateX: -totalWidth,
-                easing: "easeInOutQuad",
-                duration: 500
-            });
-        }
-    }
-
-    @action.bound
-    onclick() {
-        this._forwardTimeline.play();
-        this._forwardTimeline.reverse();
-        this.props.handleChange();
-    }
-
-    @action.bound
-    public resetToggle = () => {
-        if (!this.props.getStatus()) {
-            this._forwardTimeline.play();
-            this._forwardTimeline.reverse();
-            this.props.handleChange();
-        }
-    }
-
-    render() {
-        return (
-            <div>
-                <div className="toggle-title">
-                    <div className="toggle-option" style={{ opacity: (this.props.getStatus() ? 1 : .4) }}>{this.props.optionOne}</div>
-                    <div className="toggle-option" style={{ opacity: (this.props.getStatus() ? .4 : 1) }}>{this.props.optionTwo}</div>
-                </div>
-                <div className="toggle-bar" id="toggle-bar" onClick={this.onclick} style={{ flexDirection: (this._originalStatus ? "row" : "row-reverse") }}>
-                    <div className="toggle-button" id="toggle-button" ref={this._toggleButton} />
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 8ac20b1e5..817b62373 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -34,7 +34,7 @@ export abstract class URLField extends ObjectField {
         if (Utils.prepend(this.url?.pathname) === this.url?.href) {
             return `new ${this.constructor.name}("${this.url.pathname}")`;
         }
-        return `new ${this.constructor.name}("${this.url.href}")`;
+        return `new ${this.constructor.name}("${this.url?.href}")`;
     }
     [ToString]() {
         if (Utils.prepend(this.url?.pathname) === this.url?.href) {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index ab3982cc5..cd9b635bd 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -121,8 +121,8 @@ export namespace DashUploadUtils {
     }
 
     function resolveExistingFile(name: string, pat: string, directory: Directory, mimetype?: string | null, duration?: number, rawText?: string): Upload.FileResponse<Upload.FileInformation> {
-        const data = { size: 0, filepath: pat, name, type: mimetype ?? '', originalFilename: name, newFilename: path.basename(pat), mimetype, hashAlgorithm: false as any };
-        const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), mimetype, toJson: () => undefined as any }) };
+        const data = { size: 0, filepath: pat, name, type: mimetype ?? '', originalFilename: name, newFilename: path.basename(pat), mimetype: mimetype || null, hashAlgorithm: false as any };
+        const file = { ...data, toJSON: () => ({ ...data, length: 0, filename: data.filepath.replace(/.*\//, ''), mtime: new Date(), mimetype: mimetype || null, toJson: () => undefined as any }) };
         return {
             source: file || null,
             result: {
-- 
cgit v1.2.3-70-g09d2

