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

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

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

diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index d157e7b1c..196250167 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -21,7 +21,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
     @computed get _docs() {
         if (!this.props.DocView) return [];
         const target = this.props.DocView.props.Document;
-        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.rootDoc;
+        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.Document;
         const embeddings = DocListCast(target.proto_embeddings);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
-- 
cgit v1.2.3-70-g09d2


From be2569d8640f1693eb27f124ad3dd8062ada4837 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 11 Dec 2023 00:41:05 -0500
Subject: more updates to mobx 6.  updated typescript to v5. updated pdf-dist

---
 package-lock.json                                  | 1521 ++------------------
 package.json                                       |    9 +-
 src/client/views/ContextMenu.tsx                   |    1 -
 src/client/views/DashboardView.tsx                 |    7 +-
 src/client/views/MainView.tsx                      |    7 +-
 src/client/views/PropertiesDocContextSelector.tsx  |   27 +-
 src/client/views/SidebarAnnos.tsx                  |   77 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |    2 +-
 .../collectionSchema/SchemaTableCell.tsx           |    6 +-
 src/client/views/linking/LinkMenu.tsx              |    4 +-
 src/client/views/linking/LinkMenuItem.tsx          |    6 +-
 src/client/views/nodes/DocumentView.tsx            |    2 +-
 src/client/views/nodes/LinkAnchorBox.tsx           |    4 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   96 +-
 src/client/views/nodes/PDFBox.tsx                  |    3 +-
 src/client/views/nodes/WebBox.tsx                  |  154 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   49 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |    4 +-
 .../formattedText/FormattedTextBoxComment.tsx      |    4 +-
 src/client/views/pdf/PDFViewer.tsx                 |   32 +-
 src/server/ApiManagers/PDFManager.ts               |   50 +-
 src/server/DashUploadUtils.ts                      |   25 +-
 src/typings/index.d.ts                             |    1 +
 webpack.config.js                                  |   22 +-
 24 files changed, 477 insertions(+), 1636 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 9afc22828..272851e13 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
                 "@azure/storage-blob": "^12.17.0",
                 "@babel/preset-env": "^7.23.5",
                 "@babel/preset-react": "^7.23.3",
+                "@bundled-es-modules/pdfjs-dist": "^3.6.172-alpha.1",
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
                 "@ffmpeg/core": "0.12.4",
@@ -35,7 +36,7 @@
                 "@types/google-maps": "^3.2.6",
                 "@types/reveal": "^4.2.0",
                 "@types/supercluster": "^7.1.3",
-                "@types/web": "0.0.125",
+                "@types/web": "^0.0.127",
                 "@webscopeio/react-textarea-autocomplete": "^4.9.2",
                 "adm-zip": "^0.5.10",
                 "archiver": "^6.0.1",
@@ -87,7 +88,7 @@
                 "flexlayout-react": "^0.7.15",
                 "fluent-ffmpeg": "^2.1.2",
                 "forever-agent": "^0.6.1",
-                "fork-ts-checker-webpack-plugin": "^1.6.0",
+                "fork-ts-checker-webpack-plugin": "^9.0.2",
                 "formidable": "3.5.1",
                 "function-plot": "^1.23.3",
                 "golden-layout": "^2.6.0",
@@ -135,7 +136,7 @@
                 "path-browserify": "^1.0.1",
                 "pdf-parse": "^1.1.1",
                 "pdfjs": "^2.4.7",
-                "pdfjs-dist": "^2.14.305",
+                "pdfjs-dist": "^4.0.269",
                 "probe-image-size": "^7.2.3",
                 "process": "^0.11.10",
                 "prosemirror-commands": "^1.5.2",
@@ -275,7 +276,7 @@
                 "ts-loader": "^9.5.1",
                 "ts-node": "^10.9.1",
                 "ts-node-dev": "^2.0.0",
-                "typescript": "^4.7.4",
+                "typescript": "^5.3.3",
                 "webpack-dev-server": "^4.15.1",
                 "webpack-hot-middleware": "^2.25.4"
             }
@@ -2245,6 +2246,11 @@
                 "node": ">=6.9.0"
             }
         },
+        "node_modules/@bundled-es-modules/pdfjs-dist": {
+            "version": "3.6.172-alpha.1",
+            "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-3.6.172-alpha.1.tgz",
+            "integrity": "sha512-edQXn/5UR1LU1rPoFaZCBk22X8COSO4xctUjakkX6YtZ8BYT9J4EStU8X4bqX7ht2FLC3J21YMQ6RPDmut/l2g=="
+        },
         "node_modules/@cspotcode/source-map-support": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -5076,9 +5082,9 @@
             "dev": true
         },
         "node_modules/@types/web": {
-            "version": "0.0.125",
-            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.125.tgz",
-            "integrity": "sha512-TGZ4jAe1Nj4x+/wZICAZhAM8K3RIqti90ye0onlprDarBH0nLImnyZ2S729X+mzab3EMBu3KWd/KNwJbKHRV4g=="
+            "version": "0.0.127",
+            "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.127.tgz",
+            "integrity": "sha512-5PR8ULDp5pYzBWke+VKihf2TkE2H5+RyyGJSDFAk+R1dWUKpwPoW/OiWv0q7DaLeJ/qvXPoKSuV331VsaEneew=="
         },
         "node_modules/@types/webidl-conversions": {
             "version": "7.0.3",
@@ -5776,30 +5782,6 @@
                 "dequal": "^2.0.3"
             }
         },
-        "node_modules/arr-diff": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
-            "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/arr-flatten": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
-            "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/arr-union": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
-            "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/array-back": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz",
@@ -5981,14 +5963,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/array-unique": {
-            "version": "0.3.2",
-            "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-            "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/array.prototype.findlastindex": {
             "version": "1.2.3",
             "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
@@ -6148,14 +6122,6 @@
                 "node": "*"
             }
         },
-        "node_modules/assign-symbols": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
-            "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/ast-types-flow": {
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -6176,17 +6142,6 @@
             "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
             "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
         },
-        "node_modules/async-each": {
-            "version": "1.0.6",
-            "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz",
-            "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==",
-            "funding": [
-                {
-                    "type": "individual",
-                    "url": "https://paulmillr.com/funding/"
-                }
-            ]
-        },
         "node_modules/asynciterator.prototype": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
@@ -6201,17 +6156,6 @@
             "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
             "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
         },
-        "node_modules/atob": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
-            "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
-            "bin": {
-                "atob": "bin/atob.js"
-            },
-            "engines": {
-                "node": ">= 4.5.0"
-            }
-        },
         "node_modules/available-typed-arrays": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -6293,71 +6237,6 @@
                 "babel-node": "lib/cli.js"
             }
         },
-        "node_modules/babel-code-frame": {
-            "version": "6.26.0",
-            "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
-            "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
-            "dependencies": {
-                "chalk": "^1.1.3",
-                "esutils": "^2.0.2",
-                "js-tokens": "^3.0.2"
-            }
-        },
-        "node_modules/babel-code-frame/node_modules/ansi-regex": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-            "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/babel-code-frame/node_modules/ansi-styles": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-            "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/babel-code-frame/node_modules/chalk": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-            "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
-            "dependencies": {
-                "ansi-styles": "^2.2.1",
-                "escape-string-regexp": "^1.0.2",
-                "has-ansi": "^2.0.0",
-                "strip-ansi": "^3.0.0",
-                "supports-color": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/babel-code-frame/node_modules/js-tokens": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
-            "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg=="
-        },
-        "node_modules/babel-code-frame/node_modules/strip-ansi": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
-            "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
-            "dependencies": {
-                "ansi-regex": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/babel-code-frame/node_modules/supports-color": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-            "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
-            "engines": {
-                "node": ">=0.8.0"
-            }
-        },
         "node_modules/babel-eslint": {
             "version": "10.1.0",
             "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -6543,51 +6422,11 @@
             "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
             "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
         },
-        "node_modules/base": {
-            "version": "0.11.2",
-            "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
-            "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
-            "dependencies": {
-                "cache-base": "^1.0.1",
-                "class-utils": "^0.3.5",
-                "component-emitter": "^1.2.1",
-                "define-property": "^1.0.0",
-                "isobject": "^3.0.1",
-                "mixin-deep": "^1.2.0",
-                "pascalcase": "^0.1.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/base-64": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
             "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
         },
-        "node_modules/base/node_modules/define-property": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-            "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
-            "dependencies": {
-                "is-descriptor": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/base/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/Base64": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
@@ -6700,15 +6539,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/bindings": {
-            "version": "1.5.0",
-            "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-            "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-            "optional": true,
-            "dependencies": {
-                "file-uri-to-path": "1.0.0"
-            }
-        },
         "node_modules/bingmaps-react": {
             "version": "1.2.10",
             "resolved": "https://registry.npmjs.org/bingmaps-react/-/bingmaps-react-1.2.10.tgz",
@@ -9895,25 +9725,6 @@
                 "node": ">= 0.8"
             }
         },
-        "node_modules/cache-base": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
-            "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
-            "dependencies": {
-                "collection-visit": "^1.0.0",
-                "component-emitter": "^1.2.1",
-                "get-value": "^2.0.6",
-                "has-value": "^1.0.0",
-                "isobject": "^3.0.1",
-                "set-value": "^2.0.0",
-                "to-object-path": "^0.3.0",
-                "union-value": "^1.0.0",
-                "unset-value": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/cacheable-lookup": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
@@ -10229,20 +10040,6 @@
             "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
             "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
         },
-        "node_modules/class-utils": {
-            "version": "0.3.6",
-            "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
-            "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
-            "dependencies": {
-                "arr-union": "^3.1.0",
-                "define-property": "^0.2.5",
-                "isobject": "^3.0.0",
-                "static-extend": "^0.1.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/classnames": {
             "version": "2.3.2",
             "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
@@ -10372,18 +10169,6 @@
                 "node": ">=6"
             }
         },
-        "node_modules/collection-visit": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
-            "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==",
-            "dependencies": {
-                "map-visit": "^1.0.0",
-                "object-visit": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/color": {
             "version": "4.2.3",
             "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -10502,14 +10287,6 @@
             "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
             "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="
         },
-        "node_modules/component-emitter": {
-            "version": "1.3.1",
-            "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
-            "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
-            }
-        },
         "node_modules/compress-commons": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz",
@@ -10770,14 +10547,6 @@
                 "node": ">= 0.8"
             }
         },
-        "node_modules/copy-descriptor": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
-            "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/core-js": {
             "version": "3.34.0",
             "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz",
@@ -11884,17 +11653,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/define-property": {
-            "version": "0.2.5",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-            "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
-            "dependencies": {
-                "is-descriptor": "^0.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/delaunator": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz",
@@ -12374,12 +12132,6 @@
                 "url": "https://github.com/fb55/domhandler?sponsor=1"
             }
         },
-        "node_modules/dommatrix": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
-            "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
-            "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix."
-        },
         "node_modules/domutils": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
@@ -14544,36 +14296,6 @@
             "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz",
             "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw=="
         },
-        "node_modules/expand-brackets": {
-            "version": "2.1.4",
-            "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
-            "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==",
-            "dependencies": {
-                "debug": "^2.3.3",
-                "define-property": "^0.2.5",
-                "extend-shallow": "^2.0.1",
-                "posix-character-classes": "^0.1.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/expand-brackets/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/expand-brackets/node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        },
         "node_modules/expand-tilde": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -14814,17 +14536,6 @@
             "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
             "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
         },
-        "node_modules/extend-shallow": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-            "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
-            "dependencies": {
-                "is-extendable": "^0.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/external-editor": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -14851,47 +14562,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/extglob": {
-            "version": "2.0.4",
-            "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
-            "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
-            "dependencies": {
-                "array-unique": "^0.3.2",
-                "define-property": "^1.0.0",
-                "expand-brackets": "^2.1.4",
-                "extend-shallow": "^2.0.1",
-                "fragment-cache": "^0.2.1",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/extglob/node_modules/define-property": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-            "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
-            "dependencies": {
-                "is-descriptor": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/extglob/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/extsprintf": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -15089,12 +14759,6 @@
                 "url": "https://github.com/sindresorhus/file-type?sponsor=1"
             }
         },
-        "node_modules/file-uri-to-path": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-            "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-            "optional": true
-        },
         "node_modules/fill-range": {
             "version": "7.0.1",
             "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -15410,14 +15074,6 @@
                 "for-each-property": "0.0.4"
             }
         },
-        "node_modules/for-in": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
-            "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/foreground-child": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@@ -15453,299 +15109,189 @@
             }
         },
         "node_modules/fork-ts-checker-webpack-plugin": {
-            "version": "1.6.0",
-            "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.6.0.tgz",
-            "integrity": "sha512-vqOY5gakcoon2s12V7MMe01OPwfgqulUWFzm+geQaPPOBKjW1I7aqqoBVlU0ECn97liMB0ECs16pRdIGe9qdRw==",
+            "version": "9.0.2",
+            "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz",
+            "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==",
             "dependencies": {
-                "babel-code-frame": "^6.22.0",
-                "chalk": "^2.4.1",
-                "chokidar": "^2.0.4",
-                "micromatch": "^3.1.10",
+                "@babel/code-frame": "^7.16.7",
+                "chalk": "^4.1.2",
+                "chokidar": "^3.5.3",
+                "cosmiconfig": "^8.2.0",
+                "deepmerge": "^4.2.2",
+                "fs-extra": "^10.0.0",
+                "memfs": "^3.4.1",
                 "minimatch": "^3.0.4",
-                "semver": "^5.6.0",
-                "tapable": "^1.0.0",
-                "worker-rpc": "^0.1.0"
+                "node-abort-controller": "^3.0.1",
+                "schema-utils": "^3.1.1",
+                "semver": "^7.3.5",
+                "tapable": "^2.2.1"
             },
             "engines": {
-                "node": ">=6.11.5",
+                "node": ">=12.13.0",
                 "yarn": ">=1.0.0"
+            },
+            "peerDependencies": {
+                "typescript": ">3.6.0",
+                "webpack": "^5.11.0"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/anymatch": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
-            "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
-            "dependencies": {
-                "micromatch": "^3.1.4",
-                "normalize-path": "^2.1.1"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/anymatch/node_modules/normalize-path": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-            "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
             "dependencies": {
-                "remove-trailing-separator": "^1.0.1"
+                "color-convert": "^2.0.1"
             },
             "engines": {
-                "node": ">=0.10.0"
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/binary-extensions": {
-            "version": "1.13.1",
-            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
-            "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/argparse": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/braces": {
-            "version": "2.3.2",
-            "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
-            "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+            "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
             "dependencies": {
-                "arr-flatten": "^1.1.0",
-                "array-unique": "^0.3.2",
-                "extend-shallow": "^2.0.1",
-                "fill-range": "^4.0.0",
-                "isobject": "^3.0.1",
-                "repeat-element": "^1.1.2",
-                "snapdragon": "^0.8.1",
-                "snapdragon-node": "^2.0.1",
-                "split-string": "^3.0.2",
-                "to-regex": "^3.0.1"
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
             },
             "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": {
-            "version": "2.1.8",
-            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
-            "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
-            "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies",
-            "dependencies": {
-                "anymatch": "^2.0.0",
-                "async-each": "^1.0.1",
-                "braces": "^2.3.2",
-                "glob-parent": "^3.1.0",
-                "inherits": "^2.0.3",
-                "is-binary-path": "^1.0.0",
-                "is-glob": "^4.0.0",
-                "normalize-path": "^3.0.0",
-                "path-is-absolute": "^1.0.0",
-                "readdirp": "^2.2.1",
-                "upath": "^1.1.1"
+                "node": ">=10"
             },
-            "optionalDependencies": {
-                "fsevents": "^1.2.7"
+            "funding": {
+                "url": "https://github.com/chalk/chalk?sponsor=1"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/define-property": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
-            "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
             "dependencies": {
-                "is-descriptor": "^1.0.2",
-                "isobject": "^3.0.1"
+                "color-name": "~1.1.4"
             },
             "engines": {
-                "node": ">=0.10.0"
+                "node": ">=7.0.0"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/fill-range": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-            "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+        },
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": {
+            "version": "8.3.6",
+            "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
+            "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
             "dependencies": {
-                "extend-shallow": "^2.0.1",
-                "is-number": "^3.0.0",
-                "repeat-string": "^1.6.1",
-                "to-regex-range": "^2.1.0"
+                "import-fresh": "^3.3.0",
+                "js-yaml": "^4.1.0",
+                "parse-json": "^5.2.0",
+                "path-type": "^4.0.0"
             },
             "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/fsevents": {
-            "version": "1.2.13",
-            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
-            "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
-            "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2",
-            "hasInstallScript": true,
-            "optional": true,
-            "os": [
-                "darwin"
-            ],
-            "dependencies": {
-                "bindings": "^1.5.0",
-                "nan": "^2.12.1"
+                "node": ">=14"
             },
-            "engines": {
-                "node": ">= 4.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/glob-parent": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
-            "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
-            "dependencies": {
-                "is-glob": "^3.1.0",
-                "path-dirname": "^1.0.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/glob-parent/node_modules/is-glob": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
-            "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==",
-            "dependencies": {
-                "is-extglob": "^2.1.0"
+            "funding": {
+                "url": "https://github.com/sponsors/d-fischer"
             },
-            "engines": {
-                "node": ">=0.10.0"
+            "peerDependencies": {
+                "typescript": ">=4.9.5"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-binary-path": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-            "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==",
-            "dependencies": {
-                "binary-extensions": "^1.0.0"
-            },
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/deepmerge": {
+            "version": "4.3.1",
+            "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+            "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
             "engines": {
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-buffer": {
-            "version": "1.1.6",
-            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-            "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": {
+            "version": "10.1.0",
+            "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+            "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
             "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
+                "graceful-fs": "^4.2.0",
+                "jsonfile": "^6.0.1",
+                "universalify": "^2.0.0"
             },
             "engines": {
-                "node": ">= 0.4"
+                "node": ">=12"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
             "engines": {
-                "node": ">=0.10.0"
+                "node": ">=8"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-            "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/js-yaml": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
             "dependencies": {
-                "kind-of": "^3.0.2"
+                "argparse": "^2.0.1"
             },
-            "engines": {
-                "node": ">=0.10.0"
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/is-number/node_modules/kind-of": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-            "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/jsonfile": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+            "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
             "dependencies": {
-                "is-buffer": "^1.1.5"
+                "universalify": "^2.0.0"
             },
-            "engines": {
-                "node": ">=0.10.0"
+            "optionalDependencies": {
+                "graceful-fs": "^4.1.6"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/micromatch": {
-            "version": "3.1.10",
-            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
-            "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": {
+            "version": "7.5.4",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+            "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
             "dependencies": {
-                "arr-diff": "^4.0.0",
-                "array-unique": "^0.3.2",
-                "braces": "^2.3.1",
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "extglob": "^2.0.4",
-                "fragment-cache": "^0.2.1",
-                "kind-of": "^6.0.2",
-                "nanomatch": "^1.2.9",
-                "object.pick": "^1.3.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.2"
+                "lru-cache": "^6.0.0"
             },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/micromatch/node_modules/extend-shallow": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
-            "dependencies": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
+            "bin": {
+                "semver": "bin/semver.js"
             },
             "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/readable-stream": {
-            "version": "2.3.8",
-            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-            "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-            "dependencies": {
-                "core-util-is": "~1.0.0",
-                "inherits": "~2.0.3",
-                "isarray": "~1.0.0",
-                "process-nextick-args": "~2.0.0",
-                "safe-buffer": "~5.1.1",
-                "string_decoder": "~1.1.1",
-                "util-deprecate": "~1.0.1"
+                "node": ">=10"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
-            "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+            "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
             "dependencies": {
-                "graceful-fs": "^4.1.11",
-                "micromatch": "^3.1.10",
-                "readable-stream": "^2.0.2"
+                "has-flag": "^4.0.0"
             },
             "engines": {
-                "node": ">=0.10"
-            }
-        },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
-            "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
-            "engines": {
-                "node": ">=6"
+                "node": ">=8"
             }
         },
-        "node_modules/fork-ts-checker-webpack-plugin/node_modules/to-regex-range": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
-            "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==",
-            "dependencies": {
-                "is-number": "^3.0.0",
-                "repeat-string": "^1.6.1"
-            },
+        "node_modules/fork-ts-checker-webpack-plugin/node_modules/universalify": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+            "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
             "engines": {
-                "node": ">=0.10.0"
+                "node": ">= 10.0.0"
             }
         },
         "node_modules/form-data": {
@@ -15810,17 +15356,6 @@
                 "node": ">= 0.6"
             }
         },
-        "node_modules/fragment-cache": {
-            "version": "0.2.1",
-            "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
-            "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==",
-            "dependencies": {
-                "map-cache": "^0.2.2"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/fresh": {
             "version": "0.5.2",
             "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -16193,14 +15728,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/get-value": {
-            "version": "2.0.6",
-            "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
-            "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/getpass": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@@ -16529,25 +16056,6 @@
                 "node": ">= 0.4.0"
             }
         },
-        "node_modules/has-ansi": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-            "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
-            "dependencies": {
-                "ansi-regex": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-ansi/node_modules/ansi-regex": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-            "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/has-bigints": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@@ -16616,69 +16124,6 @@
             "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
             "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
         },
-        "node_modules/has-value": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
-            "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==",
-            "dependencies": {
-                "get-value": "^2.0.6",
-                "has-values": "^1.0.0",
-                "isobject": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-values": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
-            "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==",
-            "dependencies": {
-                "is-number": "^3.0.0",
-                "kind-of": "^4.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-values/node_modules/is-buffer": {
-            "version": "1.1.6",
-            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-            "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-        },
-        "node_modules/has-values/node_modules/is-number": {
-            "version": "3.0.0",
-            "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-            "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
-            "dependencies": {
-                "kind-of": "^3.0.2"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-values/node_modules/is-number/node_modules/kind-of": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-            "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
-            "dependencies": {
-                "is-buffer": "^1.1.5"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/has-values/node_modules/kind-of": {
-            "version": "4.0.0",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
-            "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==",
-            "dependencies": {
-                "is-buffer": "^1.1.5"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/hasown": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -17824,17 +17269,6 @@
                 "node": ">= 0.10"
             }
         },
-        "node_modules/is-accessor-descriptor": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz",
-            "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==",
-            "dependencies": {
-                "hasown": "^2.0.0"
-            },
-            "engines": {
-                "node": ">= 0.10"
-            }
-        },
         "node_modules/is-alphabetical": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -17986,17 +17420,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/is-data-descriptor": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz",
-            "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==",
-            "dependencies": {
-                "hasown": "^2.0.0"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/is-date-object": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -18020,18 +17443,6 @@
                 "url": "https://github.com/sponsors/wooorm"
             }
         },
-        "node_modules/is-descriptor": {
-            "version": "0.1.7",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz",
-            "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/is-docker": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@@ -18067,14 +17478,6 @@
                 "node": ">=0.4.0"
             }
         },
-        "node_modules/is-extendable": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
-            "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -19361,25 +18764,6 @@
             "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
             "dev": true
         },
-        "node_modules/map-cache": {
-            "version": "0.2.2",
-            "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
-            "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/map-visit": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
-            "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==",
-            "dependencies": {
-                "object-visit": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/markdown-table": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
@@ -19766,11 +19150,6 @@
                 "node": ">= 0.6"
             }
         },
-        "node_modules/microevent.ts": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz",
-            "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g=="
-        },
         "node_modules/micromark": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
@@ -20452,29 +19831,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/mixin-deep": {
-            "version": "1.3.2",
-            "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
-            "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
-            "dependencies": {
-                "for-in": "^1.0.2",
-                "is-extendable": "^1.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/mixin-deep/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/mkdirp": {
             "version": "0.5.4",
             "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
@@ -21404,74 +20760,6 @@
             "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
             "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ=="
         },
-        "node_modules/nanomatch": {
-            "version": "1.2.13",
-            "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
-            "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
-            "dependencies": {
-                "arr-diff": "^4.0.0",
-                "array-unique": "^0.3.2",
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "fragment-cache": "^0.2.1",
-                "is-windows": "^1.0.2",
-                "kind-of": "^6.0.2",
-                "object.pick": "^1.3.0",
-                "regex-not": "^1.0.0",
-                "snapdragon": "^0.8.1",
-                "to-regex": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/nanomatch/node_modules/define-property": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
-            "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
-            "dependencies": {
-                "is-descriptor": "^1.0.2",
-                "isobject": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/nanomatch/node_modules/extend-shallow": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
-            "dependencies": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/nanomatch/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
-        "node_modules/nanomatch/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/natural-compare": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -21541,6 +20829,11 @@
                 "tslib": "^2.0.3"
             }
         },
+        "node_modules/node-abort-controller": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
+            "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
+        },
         "node_modules/node-domexception": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
@@ -24533,35 +23826,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/object-copy": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
-            "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==",
-            "dependencies": {
-                "copy-descriptor": "^0.1.0",
-                "define-property": "^0.2.5",
-                "kind-of": "^3.0.3"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/object-copy/node_modules/is-buffer": {
-            "version": "1.1.6",
-            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-            "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-        },
-        "node_modules/object-copy/node_modules/kind-of": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-            "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
-            "dependencies": {
-                "is-buffer": "^1.1.5"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/object-inspect": {
             "version": "1.13.1",
             "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -24623,17 +23887,6 @@
                 "pipe-functions": "^1.2.0"
             }
         },
-        "node_modules/object-visit": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
-            "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==",
-            "dependencies": {
-                "isobject": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/object.assign": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
@@ -24722,17 +23975,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/object.pick": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
-            "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==",
-            "dependencies": {
-                "isobject": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/object.values": {
             "version": "1.1.7",
             "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
@@ -25136,14 +24378,6 @@
                 "tslib": "^2.0.3"
             }
         },
-        "node_modules/pascalcase": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
-            "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/passport": {
             "version": "0.7.0",
             "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
@@ -25215,11 +24449,6 @@
             "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
             "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
         },
-        "node_modules/path-dirname": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
-            "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q=="
-        },
         "node_modules/path-exists": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
@@ -25294,6 +24523,15 @@
                 "node": ">=8"
             }
         },
+        "node_modules/path2d-polyfill": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz",
+            "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
+            "optional": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/pathval": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
@@ -25336,20 +24574,15 @@
             }
         },
         "node_modules/pdfjs-dist": {
-            "version": "2.16.105",
-            "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz",
-            "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==",
-            "dependencies": {
-                "dommatrix": "^1.0.3",
-                "web-streams-polyfill": "^3.2.1"
-            },
-            "peerDependencies": {
-                "worker-loader": "^3.0.8"
+            "version": "4.0.269",
+            "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.0.269.tgz",
+            "integrity": "sha512-jjWO56tcOjnmPqDf8PmXDeZ781AGvpHMYI3HhNtaFKTRXXPaD1ArSrhVe38/XsrIQJ0onISCND/vuXaWJkiDWw==",
+            "engines": {
+                "node": ">=18"
             },
-            "peerDependenciesMeta": {
-                "worker-loader": {
-                    "optional": true
-                }
+            "optionalDependencies": {
+                "canvas": "^2.11.2",
+                "path2d-polyfill": "^2.0.1"
             }
         },
         "node_modules/pdfjs/node_modules/pako": {
@@ -25515,14 +24748,6 @@
                 "node": ">=4.0.0"
             }
         },
-        "node_modules/posix-character-classes": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
-            "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/postcss": {
             "version": "8.4.32",
             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
@@ -26816,41 +26041,6 @@
                 "@babel/runtime": "^7.8.4"
             }
         },
-        "node_modules/regex-not": {
-            "version": "1.0.2",
-            "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
-            "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
-            "dependencies": {
-                "extend-shallow": "^3.0.2",
-                "safe-regex": "^1.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/regex-not/node_modules/extend-shallow": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
-            "dependencies": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/regex-not/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/regexp.prototype.flags": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -26998,11 +26188,6 @@
                 "url": "https://opencollective.com/unified"
             }
         },
-        "node_modules/remove-trailing-separator": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
-            "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw=="
-        },
         "node_modules/renderkid": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
@@ -27100,22 +26285,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/repeat-element": {
-            "version": "1.1.4",
-            "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
-            "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/repeat-string": {
-            "version": "1.6.1",
-            "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
-            "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
-            "engines": {
-                "node": ">=0.10"
-            }
-        },
         "node_modules/request": {
             "version": "2.88.2",
             "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@@ -27308,12 +26477,6 @@
                 "node": ">=4"
             }
         },
-        "node_modules/resolve-url": {
-            "version": "0.2.1",
-            "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-            "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==",
-            "deprecated": "https://github.com/lydell/resolve-url#deprecated"
-        },
         "node_modules/responselike": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
@@ -27341,14 +26504,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/ret": {
-            "version": "0.1.15",
-            "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
-            "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
-            "engines": {
-                "node": ">=0.12"
-            }
-        },
         "node_modules/retry": {
             "version": "0.13.1",
             "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
@@ -27631,14 +26786,6 @@
             "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
             "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
         },
-        "node_modules/safe-regex": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
-            "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==",
-            "dependencies": {
-                "ret": "~0.1.10"
-            }
-        },
         "node_modules/safe-regex-test": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -27981,20 +27128,6 @@
                 "node": ">= 0.4"
             }
         },
-        "node_modules/set-value": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
-            "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
-            "dependencies": {
-                "extend-shallow": "^2.0.1",
-                "is-extendable": "^0.1.1",
-                "is-plain-object": "^2.0.3",
-                "split-string": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/setimmediate": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
@@ -28176,100 +27309,6 @@
                 "node": ">=6"
             }
         },
-        "node_modules/snapdragon": {
-            "version": "0.8.2",
-            "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
-            "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
-            "dependencies": {
-                "base": "^0.11.1",
-                "debug": "^2.2.0",
-                "define-property": "^0.2.5",
-                "extend-shallow": "^2.0.1",
-                "map-cache": "^0.2.2",
-                "source-map": "^0.5.6",
-                "source-map-resolve": "^0.5.0",
-                "use": "^3.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/snapdragon-node": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
-            "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
-            "dependencies": {
-                "define-property": "^1.0.0",
-                "isobject": "^3.0.0",
-                "snapdragon-util": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/snapdragon-node/node_modules/define-property": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-            "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
-            "dependencies": {
-                "is-descriptor": "^1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/snapdragon-node/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
-        "node_modules/snapdragon-util": {
-            "version": "3.0.1",
-            "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
-            "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
-            "dependencies": {
-                "kind-of": "^3.2.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/snapdragon-util/node_modules/is-buffer": {
-            "version": "1.1.6",
-            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-            "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-        },
-        "node_modules/snapdragon-util/node_modules/kind-of": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-            "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
-            "dependencies": {
-                "is-buffer": "^1.1.5"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/snapdragon/node_modules/debug": {
-            "version": "2.6.9",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-            "dependencies": {
-                "ms": "2.0.0"
-            }
-        },
-        "node_modules/snapdragon/node_modules/ms": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
-        },
         "node_modules/socket.io": {
             "version": "4.7.2",
             "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz",
@@ -28440,19 +27479,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/source-map-resolve": {
-            "version": "0.5.3",
-            "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
-            "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
-            "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
-            "dependencies": {
-                "atob": "^2.1.2",
-                "decode-uri-component": "^0.2.0",
-                "resolve-url": "^0.2.1",
-                "source-map-url": "^0.4.0",
-                "urix": "^0.1.0"
-            }
-        },
         "node_modules/source-map-support": {
             "version": "0.5.21",
             "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -28470,12 +27496,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/source-map-url": {
-            "version": "0.4.1",
-            "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
-            "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
-            "deprecated": "See https://github.com/lydell/source-map-url#deprecated"
-        },
         "node_modules/space-separated-tokens": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
@@ -28582,40 +27602,6 @@
             "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz",
             "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE="
         },
-        "node_modules/split-string": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
-            "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
-            "dependencies": {
-                "extend-shallow": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/split-string/node_modules/extend-shallow": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
-            "dependencies": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/split-string/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/sprintf-js": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -28658,18 +27644,6 @@
                 "standard-error": ">= 1.1.0 < 2"
             }
         },
-        "node_modules/static-extend": {
-            "version": "0.1.2",
-            "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
-            "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==",
-            "dependencies": {
-                "define-property": "^0.2.5",
-                "object-copy": "^0.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/statuses": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -29335,47 +28309,6 @@
                 "node": ">=4"
             }
         },
-        "node_modules/to-object-path": {
-            "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
-            "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==",
-            "dependencies": {
-                "kind-of": "^3.0.2"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/to-object-path/node_modules/is-buffer": {
-            "version": "1.1.6",
-            "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-            "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-        },
-        "node_modules/to-object-path/node_modules/kind-of": {
-            "version": "3.2.2",
-            "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-            "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
-            "dependencies": {
-                "is-buffer": "^1.1.5"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/to-regex": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
-            "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
-            "dependencies": {
-                "define-property": "^2.0.2",
-                "extend-shallow": "^3.0.2",
-                "regex-not": "^1.0.2",
-                "safe-regex": "^1.1.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/to-regex-range": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -29387,53 +28320,6 @@
                 "node": ">=8.0"
             }
         },
-        "node_modules/to-regex/node_modules/define-property": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
-            "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
-            "dependencies": {
-                "is-descriptor": "^1.0.2",
-                "isobject": "^3.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/to-regex/node_modules/extend-shallow": {
-            "version": "3.0.2",
-            "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
-            "dependencies": {
-                "assign-symbols": "^1.0.0",
-                "is-extendable": "^1.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/to-regex/node_modules/is-descriptor": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz",
-            "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==",
-            "dependencies": {
-                "is-accessor-descriptor": "^1.0.1",
-                "is-data-descriptor": "^1.0.1"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
-        "node_modules/to-regex/node_modules/is-extendable": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
-            "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
-            "dependencies": {
-                "is-plain-object": "^2.0.4"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/toidentifier": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -30034,15 +28920,15 @@
             }
         },
         "node_modules/typescript": {
-            "version": "4.9.5",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
-            "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+            "version": "5.3.3",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+            "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
             "bin": {
                 "tsc": "bin/tsc",
                 "tsserver": "bin/tsserver"
             },
             "engines": {
-                "node": ">=4.2.0"
+                "node": ">=14.17"
             }
         },
         "node_modules/typescript-collections": {
@@ -30179,20 +29065,6 @@
             "resolved": "https://registry.npmjs.org/uninstall/-/uninstall-0.0.0.tgz",
             "integrity": "sha512-pjP/0+A4gsbDVa8XH/S2GZdT9NPJW8NFMy3GI7HnsWG+NAmFSSj3QidNosXBI9cPtxxNExEDdhKFO6sli8K3mA=="
         },
-        "node_modules/union-value": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
-            "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
-            "dependencies": {
-                "arr-union": "^3.1.0",
-                "get-value": "^2.0.6",
-                "is-extendable": "^0.1.1",
-                "set-value": "^2.0.1"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/unist-util-is": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
@@ -30303,50 +29175,6 @@
                 "node": ">= 0.8"
             }
         },
-        "node_modules/unset-value": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
-            "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==",
-            "dependencies": {
-                "has-value": "^0.3.1",
-                "isobject": "^3.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/unset-value/node_modules/has-value": {
-            "version": "0.3.1",
-            "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
-            "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
-            "dependencies": {
-                "get-value": "^2.0.3",
-                "has-values": "^0.1.4",
-                "isobject": "^2.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/unset-value/node_modules/has-value/node_modules/isobject": {
-            "version": "2.1.0",
-            "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
-            "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
-            "dependencies": {
-                "isarray": "1.0.0"
-            },
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
-        "node_modules/unset-value/node_modules/has-values": {
-            "version": "0.1.4",
-            "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
-            "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/untildify": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -30356,15 +29184,6 @@
                 "node": ">=8"
             }
         },
-        "node_modules/upath": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
-            "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
-            "engines": {
-                "node": ">=4",
-                "yarn": "*"
-            }
-        },
         "node_modules/update-browserslist-db": {
             "version": "1.0.13",
             "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -30402,12 +29221,6 @@
                 "punycode": "^2.1.0"
             }
         },
-        "node_modules/urix": {
-            "version": "0.1.0",
-            "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
-            "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==",
-            "deprecated": "Please see https://github.com/lydell/urix#deprecated"
-        },
         "node_modules/url": {
             "version": "0.11.3",
             "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz",
@@ -30500,14 +29313,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/use": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
-            "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
-            "engines": {
-                "node": ">=0.10.0"
-            }
-        },
         "node_modules/use-isomorphic-layout-effect": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
@@ -31472,14 +30277,6 @@
                 "node": ">=4.0.0"
             }
         },
-        "node_modules/worker-rpc": {
-            "version": "0.1.1",
-            "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz",
-            "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==",
-            "dependencies": {
-                "microevent.ts": "~0.1.1"
-            }
-        },
         "node_modules/workerpool": {
             "version": "6.2.1",
             "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
diff --git a/package.json b/package.json
index 42b3c1ce2..e1efce5a3 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,7 @@
         "ts-loader": "^9.5.1",
         "ts-node": "^10.9.1",
         "ts-node-dev": "^2.0.0",
-        "typescript": "^4.7.4",
+        "typescript": "^5.3.3",
         "webpack-dev-server": "^4.15.1",
         "webpack-hot-middleware": "^2.25.4"
     },
@@ -97,6 +97,7 @@
         "@azure/storage-blob": "^12.17.0",
         "@babel/preset-env": "^7.23.5",
         "@babel/preset-react": "^7.23.3",
+        "@bundled-es-modules/pdfjs-dist": "^3.6.172-alpha.1",
         "@emotion/react": "^11.11.1",
         "@emotion/styled": "^11.11.0",
         "@ffmpeg/core": "0.12.4",
@@ -121,7 +122,7 @@
         "@types/google-maps": "^3.2.6",
         "@types/reveal": "^4.2.0",
         "@types/supercluster": "^7.1.3",
-        "@types/web": "0.0.125",
+        "@types/web": "^0.0.127",
         "@webscopeio/react-textarea-autocomplete": "^4.9.2",
         "adm-zip": "^0.5.10",
         "archiver": "^6.0.1",
@@ -173,7 +174,7 @@
         "flexlayout-react": "^0.7.15",
         "fluent-ffmpeg": "^2.1.2",
         "forever-agent": "^0.6.1",
-        "fork-ts-checker-webpack-plugin": "^1.6.0",
+        "fork-ts-checker-webpack-plugin": "^9.0.2",
         "formidable": "3.5.1",
         "function-plot": "^1.23.3",
         "golden-layout": "^2.6.0",
@@ -221,7 +222,7 @@
         "path-browserify": "^1.0.1",
         "pdf-parse": "^1.1.1",
         "pdfjs": "^2.4.7",
-        "pdfjs-dist": "^2.14.305",
+        "pdfjs-dist": "^4.0.269",
         "probe-image-size": "^7.2.3",
         "process": "^0.11.10",
         "prosemirror-commands": "^1.5.2",
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 249e2c3a3..831925054 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -204,7 +204,6 @@ export class ContextMenu extends React.Component {
     }
 
     render() {
-        console.log('DISPLAY = ' + this._display);
         return (
             <div
                 className="contextMenu-cont"
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 04fb1fe80..35c0d617b 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Button, ColorPicker, EditableText, 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 { FaPlus } from 'react-icons/fa';
@@ -39,6 +39,11 @@ enum DashboardGroup {
 export class DashboardView extends React.Component {
     public static _urlState: HistoryUtil.DocUrl;
 
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
     @observable private openModal = false;
     @observable private selectedDashboardGroup = DashboardGroup.MyDashboards;
     @observable private newDashboardName = '';
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dae2490c9..a8f4020ee 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -56,7 +56,7 @@ import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
 import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 import { ImageBox } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
-import { LinkDocPreview } from './nodes/LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
 import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu';
 import { MapBox } from './nodes/MapBox/MapBox';
 import { RadialMenu } from './nodes/RadialMenu';
@@ -968,9 +968,6 @@ export class MainView extends React.Component {
         );
     }
 
-    @computed get linkDocPreview() {
-        return LinkDocPreview.LinkInfo ? <LinkDocPreview {...LinkDocPreview.LinkInfo} /> : null;
-    }
     @observable mapBoxHackBool = false;
     @computed get mapBoxHack() {
         return this.mapBoxHackBool ? null : (
@@ -1039,7 +1036,7 @@ export class MainView extends React.Component {
                 {this._hideUI ? null : <TopBar />}
                 {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
                 {DocButtonState.Instance.LinkEditorDocView ? <LinkMenu clearLinkEditor={action(() => (DocButtonState.Instance.LinkEditorDocView = undefined))} docView={DocButtonState.Instance.LinkEditorDocView} /> : null}
-                {this.linkDocPreview}
+                {LinkInfo.Instance?.LinkInfo ? <LinkDocPreview {...LinkInfo.Instance.LinkInfo} /> : null}
 
                 {((page: string) => {
                     // prettier-ignore
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 196250167..5bde9d3c4 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../fields/Doc';
@@ -8,6 +8,7 @@ import { DocFocusOrOpen } from '../util/DocumentManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
 import { DocumentView, OpenWhere } from './nodes/DocumentView';
 import './PropertiesDocContextSelector.scss';
+import { copyProps } from '../../Utils';
 
 type PropertiesDocContextSelectorProps = {
     DocView?: DocumentView;
@@ -18,10 +19,20 @@ type PropertiesDocContextSelectorProps = {
 
 @observer
 export class PropertiesDocContextSelector extends React.Component<PropertiesDocContextSelectorProps> {
+    _prevProps: PropertiesDocContextSelectorProps;
+    @observable _props: PropertiesDocContextSelectorProps;
+    constructor(props: PropertiesDocContextSelectorProps) {
+        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;
-        const targetContext = this.props.DocView.props.docViewPath().lastElement()?.Document;
+        if (!this._props.DocView) return [];
+        const target = this._props.DocView._props.Document;
+        const targetContext = this._props.DocView._props.docViewPath().lastElement()?.Document;
         const embeddings = DocListCast(target.proto_embeddings);
         const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
         const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.proto_embeddings));
@@ -40,23 +51,23 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
         );
 
         return doclayouts
-            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document))
+            .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.Document))
             .filter(doc => !Doc.IsSystem(doc))
             .filter(doc => doc !== targetContext)
             .map(doc => ({ col: doc, target }));
     }
 
     getOnClick = (col: Doc, target: Doc) => {
-        if (!this.props.DocView) return;
+        if (!this._props.DocView) return;
         col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col;
-        DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col);
+        DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col);
     };
 
     render() {
         if (this._docs.length < 1) return undefined;
         return (
             <div>
-                {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
+                {this._props.hideTitle ? null : <p key="contexts">Contexts:</p>}
                 {this._docs.map(doc => (
                     <p key={doc.col[Id] + doc.target[Id]}>
                         <a onClick={() => this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}</a>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 2a52d86c2..19473de2b 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -1,11 +1,11 @@
-import { computed } from 'mobx';
+import { computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 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 { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
+import { copyProps, emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
@@ -35,14 +35,21 @@ interface ExtraProps {
 }
 @observer
 export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
-    constructor(props: Readonly<FieldViewProps & ExtraProps>) {
+    _prevProps: FieldViewProps & ExtraProps;
+    @observable _props: FieldViewProps & ExtraProps;
+    constructor(props: FieldViewProps & ExtraProps) {
         super(props);
-        // 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
+        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>>();
-        DocListCast(this.props.Document[this.sidebarKey]).forEach(doc =>
+        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc =>
             SearchUtil.documentKeys(doc)
                 .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase())
                 .map(key => keys.set(key, doc[key]))
@@ -51,7 +58,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     }
     @computed get allHashtags() {
         const keys = new Set<string>();
-        DocListCast(this.props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
+        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag)));
         return Array.from(keys.keys())
             .filter(key => key[0])
             .filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase()))
@@ -59,7 +66,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     }
     @computed get allUsers() {
         const keys = new Set<string>();
-        DocListCast(this.props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
+        DocListCast(this._props.Document[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author)));
         return Array.from(keys.keys()).sort();
     }
 
@@ -69,7 +76,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             .join(' ');
         const target = Docs.Create.TextDocument(startup, {
             title: '-note-',
-            annotationOn: this.props.Document,
+            annotationOn: this._props.Document,
             _width: 200,
             _height: 50,
             _layout_fitWidth: true,
@@ -147,10 +154,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         return target;
     };
     makeDocUnfiltered = (doc: Doc) => {
-        if (DocListCast(this.props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
+        if (DocListCast(this._props.Document[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
             if (this.childFilters()) {
                 // if any child filters exist, get rid of them
-                this.props.layoutDoc._childFilters = new List<string>();
+                this._props.layoutDoc._childFilters = new List<string>();
             }
             return true;
         }
@@ -158,27 +165,27 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     };
 
     get sidebarKey() {
-        return this.props.fieldKey + '_sidebar';
+        return this._props.fieldKey + '_sidebar';
     }
     filtersHeight = () => 38;
     screenToLocalTransform = () =>
-        this.props
+        this._props
             .ScreenToLocalTransform()
-            .translate(Doc.NativeWidth(this.props.dataDoc), 0)
-            .scale(this.props.NativeDimScaling?.() || 1);
+            .translate(Doc.NativeWidth(this._props.dataDoc), 0)
+            .scale(this._props.NativeDimScaling?.() || 1);
     panelWidth = () =>
-        !this.props.showSidebar
+        !this._props.showSidebar
             ? 0
-            : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any)
-            ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
-            : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
-    panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
-    addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey);
-    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
-    removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
-    childFilters = () => StrListCast(this.props.layoutDoc._childFilters);
+            : this._props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this._props.layoutDoc.type as any)
+              ? this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1)
+              : ((NumCast(this._props.nativeWidth) - Doc.NativeWidth(this._props.dataDoc)) * this._props.PanelWidth()) / NumCast(this._props.nativeWidth);
+    panelHeight = () => this._props.PanelHeight() - this.filtersHeight();
+    addDocument = (doc: Doc | Doc[]) => this._props.sidebarAddDocument(doc, this.sidebarKey);
+    moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey);
+    removeDocument = (doc: Doc | Doc[]) => this._props.removeDocument(doc, this.sidebarKey);
+    childFilters = () => StrListCast(this._props.layoutDoc._childFilters);
     layout_showTitle = () => 'title';
-    setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
+    setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight());
     sortByLinkAnchorY = (a: Doc, b: Doc) => {
         const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].link_anchor_1).y;
         const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].link_anchor_1).y;
@@ -188,7 +195,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         const renderTag = (tag: string) => {
             const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'tags', tag, 'check', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
@@ -196,7 +203,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         const renderMeta = (tag: string, dflt: FieldResult<Field>) => {
             const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`);
             return (
-                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
+                <div key={tag} className={`sidebarAnnos-filterTag${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}>
                     {tag}
                 </div>
             );
@@ -204,20 +211,20 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         const renderUsers = (user: string) => {
             const active = this.childFilters().includes(`author:${user}:check`);
             return (
-                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this.props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}>
+                <div key={user} className={`sidebarAnnos-filterUser${active ? '-active' : ''}`} onClick={e => Doc.setDocFilter(this._props.Document, 'author', user, 'check', true, undefined, e.shiftKey)}>
                     {user}
                 </div>
             );
         };
         // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos
-        return !this.props.showSidebar ? null : (
+        return !this._props.showSidebar ? null : (
             <div
                 style={{
                     position: 'absolute',
-                    pointerEvents: this.props.isContentActive() ? 'all' : undefined,
-                    top: this.props.Document.type !== DocumentType.RTF && StrCast(this.props.Document._layout_showTitle) === 'title' ? 15 : 0,
+                    pointerEvents: this._props.isContentActive() ? 'all' : undefined,
+                    top: this._props.Document.type !== DocumentType.RTF && StrCast(this._props.Document._layout_showTitle) === 'title' ? 15 : 0,
                     right: 0,
-                    background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.WidgetColor),
+                    background: this._props.styleProvider?.(this._props.Document, this._props, StyleProp.WidgetColor),
                     width: `100%`,
                     height: '100%',
                 }}>
@@ -231,7 +238,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
 
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
-                        {...this.props}
+                        {...this._props}
                         setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
@@ -246,14 +253,14 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         NativeDimScaling={returnOne}
                         //childlayout_showTitle={this.layout_showTitle}
                         isAnyChildContentActive={returnFalse}
-                        childDocumentsActive={this.props.isContentActive}
-                        whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                        childDocumentsActive={this._props.isContentActive}
+                        whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                         childHideDecorationTitle={true}
                         removeDocument={this.removeDocument}
                         moveDocument={this.moveDocument}
                         addDocument={this.addDocument}
                         ScreenToLocalTransform={this.screenToLocalTransform}
-                        renderDepth={this.props.renderDepth + 1}
+                        renderDepth={this._props.renderDepth + 1}
                         type_collection={CollectionViewType.Stacking}
                         fieldKey={this.sidebarKey}
                         pointerEvents={returnAll}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 154c914ad..d16599bab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1441,7 +1441,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         return anchor;
     };
 
-    infoUI = () => <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />;
+    infoUI = () => (this.Document.annotationOn ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} />);
 
     componentDidMount() {
         this._props.setContentView?.(this);
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index af0fe73c3..bcfe2c232 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -121,7 +121,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
                 <EditableView
                     oneLine={this._props.oneLine}
                     allowCRs={this._props.allowCRs}
-                    contents={<FieldView {...fieldProps} />}
+                    contents={undefined}
+                    fieldContents={fieldProps}
                     editing={this.selected ? undefined : false}
                     GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
                     SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => {
@@ -333,7 +334,8 @@ export class SchemaBoolCell extends React.Component<SchemaTableCellProps> {
                     })}
                 />
                 <EditableView
-                    contents={<FieldView {...fieldProps} />}
+                    contents={undefined}
+                    fieldContents={fieldProps}
                     editing={this.selected ? undefined : false}
                     GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)}
                     SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 91d812f35..af22f41e1 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
 import { Doc } from '../../../fields/Doc';
 import { LinkManager } from '../../util/LinkManager';
 import { DocumentView } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenu.scss';
 import { LinkMenuGroup } from './LinkMenuGroup';
 import * as React from 'react';
@@ -38,7 +38,7 @@ export class LinkMenu extends React.Component<Props> {
     }
 
     onPointerDown = action((e: PointerEvent) => {
-        LinkDocPreview.Clear();
+        LinkInfo.Clear();
         if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) {
             this.clear();
         }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 858cc2e5e..d77525e04 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -15,7 +15,7 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { SettingsManager } from '../../util/SettingsManager';
 import { undoBatch } from '../../util/UndoManager';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../nodes/DocumentView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview';
 import './LinkMenuItem.scss';
 import * as React from 'react';
 
@@ -180,11 +180,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                         </div>
                         <div
                             className="linkMenu-text"
-                            onPointerLeave={LinkDocPreview.Clear}
+                            onPointerLeave={LinkInfo.Clear}
                             onPointerEnter={e =>
                                 this._props.linkDoc &&
                                 this._props.clearLinkEditor &&
-                                LinkDocPreview.SetLinkInfo({
+                                LinkInfo.SetLinkInfo({
                                     docProps: this._props.docView._props,
                                     linkSrc: this._props.sourceDoc,
                                     linkDoc: this._props.linkDoc,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b3acb08e2..abe59feb5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -136,7 +136,7 @@ export interface DocComponentView {
     componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
     dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
     incrementalRendering?: () => void;
-    infoUI?: () => JSX.Element;
+    infoUI?: () => JSX.Element | null;
     getCenter?: (xf: Transform) => { X: number; Y: number };
     screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 1b5161e47..33c9b2e08 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -11,7 +11,7 @@ import { ViewBoxBaseComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
-import { LinkDocPreview } from './LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from './LinkDocPreview';
 import * as React from 'react';
 // import globalCssVariables = require('../global/globalCssVariables.scss');
 const MEDIUM_GRAY = 'lightGray';
@@ -85,7 +85,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 title={targetTitle}
                 className={`linkAnchorBox-cont${small ? '-small' : ''}`}
                 onPointerEnter={e =>
-                    LinkDocPreview.SetLinkInfo({
+                    LinkInfo.SetLinkInfo({
                         docProps: this.props,
                         linkSrc: this.linkSource,
                         linkDoc: this.Document,
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index dd102edef..108930ad9 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,11 +1,11 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 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 { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { copyProps, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
 import { DocServer } from '../../DocServer';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -20,6 +20,25 @@ import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView
 import './LinkDocPreview.scss';
 import * as React from 'react';
 
+export class LinkInfo {
+    private static _instance: Opt<LinkInfo>;
+    constructor() {
+        LinkInfo._instance = this;
+        makeObservable(this);
+    }
+    @observable public LinkInfo: Opt<LinkDocPreviewProps> = undefined;
+
+    public static get Instance() {
+        return LinkInfo._instance ?? new LinkInfo();
+    }
+    public static Clear() {
+        runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = undefined));
+    }
+    public static SetLinkInfo(info?: LinkDocPreviewProps) {
+        runInAction(() => LinkInfo.Instance && (LinkInfo.Instance.LinkInfo = info));
+    }
+}
+
 interface LinkDocPreviewProps {
     linkDoc?: Doc;
     linkSrc?: Doc;
@@ -31,35 +50,28 @@ interface LinkDocPreviewProps {
 }
 @observer
 export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
-    @action public static Clear() {
-        LinkDocPreview.LinkInfo = undefined;
-    }
-    @action public static SetLinkInfo(info?: LinkDocPreviewProps) {
-        LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info);
+    _prevProps: LinkDocPreviewProps;
+    @observable _props: LinkDocPreviewProps;
+    constructor(props: LinkDocPreviewProps) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
     }
 
-    static _instance: Opt<LinkDocPreview>;
-
     _infoRef = React.createRef<HTMLDivElement>();
     _linkDocRef = React.createRef<HTMLDivElement>();
-    @observable public static LinkInfo: Opt<LinkDocPreviewProps>;
-    @observable _targetDoc: Opt<Doc>;
-    @observable _markerTargetDoc: Opt<Doc>;
-    @observable _linkDoc: Opt<Doc>;
-    @observable _linkSrc: Opt<Doc>;
+    @observable _targetDoc: Opt<Doc> = undefined;
+    @observable _markerTargetDoc: Opt<Doc> = undefined;
+    @observable _linkDoc: Opt<Doc> = undefined;
+    @observable _linkSrc: Opt<Doc> = undefined;
     @observable _toolTipText = '';
     @observable _hrefInd = 0;
 
-    constructor(props: any) {
-        super(props);
-        LinkDocPreview._instance = this;
-    }
-
     @action
     init() {
-        var linkTarget = this.props.linkDoc;
-        this._linkSrc = this.props.linkSrc;
-        this._linkDoc = this.props.linkDoc;
+        var linkTarget = this._props.linkDoc;
+        this._linkSrc = this._props.linkSrc;
+        this._linkDoc = this._props.linkDoc;
         const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1);
         const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2);
         if (link_anchor_1 && link_anchor_2) {
@@ -73,27 +85,27 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
         this.updateHref();
     }
     componentDidUpdate(props: any) {
-        if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
+        copyProps(this);
+        if (props.linkSrc !== this._props.linkSrc || props.linkDoc !== this._props.linkDoc || props.hrefs !== this._props.hrefs) this.init();
     }
     componentDidMount() {
         this.init();
         document.addEventListener('pointerdown', this.onPointerDown, true);
     }
 
-    @action
     componentWillUnmount() {
-        LinkDocPreview.SetLinkInfo(undefined);
+        LinkInfo.Clear();
         document.removeEventListener('pointerdown', this.onPointerDown, true);
     }
 
     onPointerDown = (e: PointerEvent) => {
-        !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+        !this._linkDocRef.current?.contains(e.target as any) && LinkInfo.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
     };
 
     @action
     updateHref() {
-        if (this.props.hrefs?.length) {
-            const href = this.props.hrefs[this._hrefInd];
+        if (this._props.hrefs?.length) {
+            const href = this._props.hrefs[this._hrefInd];
             if (href.indexOf(Doc.localServerPath()) !== 0) {
                 // link to a web page URL -- try to show a preview
                 if (href.startsWith('https://en.wikipedia.org/wiki/')) {
@@ -123,7 +135,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                                 this._markerTargetDoc = linkTarget;
                                 this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
                             }
-                            if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
+                            if (LinkInfo.Instance?.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink();
                         }
                     })
                 );
@@ -143,7 +155,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             action(() => {
                 LinkManager.currentLink = this._linkDoc;
                 LinkManager.currentLinkAnchor = this._linkSrc;
-                this.props.docProps.DocumentView?.().select(false);
+                this._props.docProps.DocumentView?.().select(false);
                 if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
                     SettingsManager.Instance.propertiesWidth = 250;
                 }
@@ -157,7 +169,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             returnFalse,
             emptyFunction,
             action(() => {
-                const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+                const nextHrefInd = (this._hrefInd + 1) % (this._props.hrefs?.length || 1);
                 if (nextHrefInd !== this._hrefInd) {
                     this._linkDoc = undefined;
                     this._hrefInd = nextHrefInd;
@@ -168,19 +180,19 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     };
 
     followLink = () => {
-        LinkDocPreview.Clear();
+        LinkInfo.Clear();
         if (this._linkDoc && this._linkSrc) {
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
-        } else if (this.props.hrefs?.length) {
+        } else if (this._props.hrefs?.length) {
             const webDoc =
-                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ??
-                Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
+                Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0]).keys()).lastElement() ??
+                Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
             DocumentManager.Instance.showDocument(webDoc, {
                 openLocation: OpenWhere.lightbox,
                 willPan: true,
                 zoomTime: 500,
             });
-            //this.props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
+            //this._props.docProps?.addDocTab(webDoc, OpenWhere.lightbox);
         }
     };
 
@@ -216,7 +228,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 </div>
                 <div className="linkDocPreview-buttonBar" style={{ float: 'right' }}>
                     <Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
-                        <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
+                        <div className="linkDocPreview-button" style={{ background: (this._props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
                             <FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
                         </div>
                     </Tooltip>
@@ -228,7 +240,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
     @computed get docPreview() {
         return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
             <div className="linkDocPreview-inner">
-                {!this.props.showHeader ? null : this.previewHeader}
+                {!this._props.showHeader ? null : this.previewHeader}
                 <div
                     className="linkDocPreview-preview-wrapper"
                     onPointerDown={e =>
@@ -238,7 +250,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                             (e, down, delta) => {
                                 if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
                                     DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
-                                    LinkDocPreview.Clear();
+                                    LinkInfo.Clear();
                                     return true;
                                 }
                                 return false;
@@ -256,11 +268,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                         <DocumentView
                             ref={r => {
                                 const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                                targetanchor && this._targetDoc !== targetanchor && r?.props.focus?.(targetanchor, {});
+                                targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {});
                             }}
                             Document={this._targetDoc!}
                             moveDocument={returnFalse}
-                            styleProvider={this.props.docProps?.styleProvider}
+                            styleProvider={this._props.docProps?.styleProvider}
                             docViewPath={returnEmptyDoclist}
                             ScreenToLocalTransform={Transform.Identity}
                             isDocumentActive={returnFalse}
@@ -299,7 +311,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                 className="linkDocPreview"
                 ref={this._linkDocRef}
                 onPointerDown={this.followLinkPointerDown}
-                style={{ borderColor: SettingsManager.userColor, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+                style={{ borderColor: SettingsManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}>
                 {this.docPreview}
             </div>
         );
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 2a884cef8..a453210eb 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -611,8 +611,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
     render() {
         TraceMobx();
-        const pdfView = this.renderPdfView;
-
+        const pdfView = !this._pdf ? null : this.renderPdfView;
         const href = this.pdfUrl?.url.href;
         if (!pdfView && href) {
             if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c722399c1..d74af9b27 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,6 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
+import * as React from 'react';
 import * as WebRequest from 'web-request';
 import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
@@ -12,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, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, copyProps, 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';
@@ -33,10 +34,9 @@ import { SidebarAnnos } from '../SidebarAnnos';
 import { StyleProp } from '../StyleProvider';
 import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
-import { LinkDocPreview } from './LinkDocPreview';
+import { LinkInfo } from './LinkDocPreview';
 import { PinProps, PresBox } from './trails';
 import './WebBox.scss';
-import * as React from 'react';
 const { CreateImage } = require('./WebBoxRenderer');
 const _global = (window /* browser */ || global) /* node */ as any;
 const htmlToText = require('html-to-text');
@@ -95,12 +95,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return this.allAnnotations.filter(a => a.text_inlineAnnotations);
     }
     @computed get webField() {
-        return Cast(this.Document[this.props.fieldKey], WebField)?.url;
+        return Cast(this.Document[this._props.fieldKey], WebField)?.url;
     }
 
-    constructor(props: any) {
+    _prevProps: ViewBoxAnnotatableProps & FieldViewProps;
+    @override _props: ViewBoxAnnotatableProps & FieldViewProps;
+    constructor(props: ViewBoxAnnotatableProps & FieldViewProps) {
         super(props);
-        runInAction(() => (this._webUrl = this._url)); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
+        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
@@ -139,7 +147,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (!this._iframe) return;
         const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
         const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
-        const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth();
+        const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
         var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
         if (!htmlString) {
             htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text();
@@ -170,8 +178,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             });
     };
 
-    async componentDidMount() {
-        this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
+    componentDidMount() {
+        this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
 
         runInAction(() => {
             this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
@@ -200,8 +208,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             () => this.layoutDoc._layout_autoHeight,
             layout_autoHeight => {
                 if (layout_autoHeight) {
-                    this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']);
-                    this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '_nativeHeight']) * (this.props.NativeDimScaling?.() || 1));
+                    this.layoutDoc._nativeHeight = NumCast(this.Document[this._props.fieldKey + '_nativeHeight']);
+                    this._props.setHeight?.(NumCast(this.Document[this._props.fieldKey + '_nativeHeight']) * (this._props.NativeDimScaling?.() || 1));
                 }
             }
         );
@@ -218,10 +226,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 }
             } // else it's an HTMLfield
         } else if (this.webField && !this.dataDoc.text) {
-            const result = await WebRequest.get(Utils.CorsProxy(this.webField.href));
-            if (result) {
-                this.dataDoc.text = htmlToText.fromString(result.content);
-            }
+            WebRequest.get(Utils.CorsProxy(this.webField.href)) //
+                .then(result => result && (this.dataDoc.text = htmlToText.convert(result.content)));
         }
 
         this._disposers.scrollReaction = reaction(
@@ -236,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             { fireImmediately: true }
         );
     }
-    @action componentWillUnmount() {
+    componentWillUnmount() {
         this._iframetimeout && clearTimeout(this._iframetimeout);
         this._iframetimeout = undefined;
         Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -251,7 +257,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @action
     createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
         if (this._mainCont.current && selRange) {
-            if (this.dataDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
+            if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`;
             const clientRects = selRange.getClientRects();
             for (let i = 0; i < clientRects.length; i++) {
                 const rect = clientRects.item(i);
@@ -259,7 +265,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 if (rect && rect.width !== this._mainCont.current.clientWidth) {
                     const annoBox = document.createElement('div');
                     annoBox.className = 'marqueeAnnotator-annotationBox';
-                    const scale = this._url ? 1 : this.props.ScreenToLocalTransform().Scale;
+                    const scale = this._url ? 1 : this._props.ScreenToLocalTransform().Scale;
                     // transforms the positions from screen onto the pdf div
                     annoBox.style.top = ((rect.top - mainrect.translateY) * scale + (this._url ? this._mainCont.current.scrollTop : NumCast(this.layoutDoc.layout_scrollTop))).toString();
                     annoBox.style.left = ((rect.left - mainrect.translateX) * scale).toString();
@@ -282,7 +288,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (anchor !== this.Document && this._outerRef.current) {
-            const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
+            const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
             const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
@@ -298,8 +304,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @action
     getView = (doc: Doc) => {
-        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.()));
-        if (this.Document.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify();
+        if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
+        if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
         const webUrl = WebCast(doc.config_data)?.url;
         if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
@@ -307,11 +313,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     sidebarAddDocTab = (doc: Doc, where: OpenWhere) => {
-        if (DocListCast(this.props.Document[this.props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
+        if (DocListCast(this.Document[this._props.fieldKey + '_sidebar']).includes(doc) && !this.SidebarShown) {
             this.toggleSidebar(false);
             return true;
         }
-        return this.props.addDocTab(doc, where);
+        return this._props.addDocTab(doc, where);
     };
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         let ele: Opt<HTMLDivElement> = undefined;
@@ -344,10 +350,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     iframeUp = (e: PointerEvent) => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
         this._textAnnotationCreator = undefined;
-        this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+        this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
         if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
             const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
-            const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale;
+            const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
             const sel = this._iframe.contentWindow.getSelection();
             if (sel) {
                 this._selectionText = sel.toString();
@@ -355,7 +361,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined);
                 AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
                 // Changing which document to add the annotation to (the currently selected WebBox)
-                GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+                GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
                 GPTPopup.Instance.addDoc = this.sidebarAddDocument;
             }
         }
@@ -393,23 +399,23 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange);
             (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY);
             // Changing which document to add the annotation to (the currently selected WebBox)
-            GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
+            GPTPopup.Instance.setSidebarId(`${this._props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`);
             GPTPopup.Instance.addDoc = this.sidebarAddDocument;
         }
     };
     @action
     iframeDown = (e: PointerEvent) => {
-        this.props.select(false);
+        this._props.select(false);
         const locpt = { 
-            x: (e.clientX / NumCast(this.Document.nativeWidth)) * this.props.PanelWidth(), 
-            y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore
-        const scrclick = this.props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
-        const scrcent = this.props
+            x: (e.clientX / NumCast(this.Document.nativeWidth)) * this._props.PanelWidth(), 
+            y: ((e.clientY - NumCast(this.layoutDoc.layout_scrollTop))/ NumCast(this.Document.nativeHeight)) * this._props.PanelHeight() }; // prettier-ignore
+        const scrclick = this._props.DocumentView?.()._props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!;
+        const scrcent = this._props
             .DocumentView?.()
-            .props.ScreenToLocalTransform()
+            ._props.ScreenToLocalTransform()
             .inverse()
             .transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2)!;
-        const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate);
+        const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this._props.ScreenToLocalTransform().Rotate);
         const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]];
         MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
         const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -489,7 +495,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0);
                 if (this._scrollHeight) {
                     this.Document.nativeHeight = Math.min(NumCast(this.Document.nativeHeight), this._scrollHeight);
-                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.dataDoc.nativeHeight)) / NumCast(this.dataDoc.nativeWidth));
+                    this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth));
                 }
             };
             const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
@@ -564,7 +570,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             clearStyleSheetRules(WebBox.webStyleSheet);
             this._scrollTimer = undefined;
             const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
-            if (!LinkDocPreview.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) {
+            if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
                 this.layoutDoc.thumb = undefined;
                 this.layoutDoc.thumbScrollTop = undefined;
                 this.layoutDoc.thumbNativeWidth = undefined;
@@ -709,7 +715,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             funcs.push({
                 description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow',
                 event: () => {
-                    const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1);
+                    const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this._props.NativeDimScaling?.() || 1);
                     this.layoutDoc.layout_reflowHorizontal = !nw;
                     if (nw) {
                         Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true);
@@ -734,7 +740,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (sel?.empty) sel.empty(); // Chrome
         else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
         this.marqueeing = [e.clientX, e.clientY];
-        if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+        if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
                 this,
                 e,
@@ -770,7 +776,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 setTimeout(() => {
                     // if menu comes up right away, the down event can still be active causing a menu item to be selected
                     this.specificContextMenu(undefined as any);
-                    this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+                    this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
                 });
             }
         }
@@ -779,7 +785,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @observable lighttext = false;
 
     @computed get urlContent() {
-        if (this.props.ScreenToLocalTransform().Scale > 25) return <div></div>;
+        if (this._props.ScreenToLocalTransform().Scale > 25) return <div></div>;
         setTimeout(
             action(() => {
                 if (this._initialScroll === undefined && !this._webPageHasBeenRendered) {
@@ -788,7 +794,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 this._webPageHasBeenRendered = true;
             })
         );
-        const field = this.dataDoc[this.props.fieldKey];
+        const field = this.dataDoc[this._props.fieldKey];
         if (field instanceof HtmlField) {
             return (
                 <span
@@ -845,14 +851,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             e,
             action((e, down, delta) => {
                 this._draggingSidebar = true;
-                const localDelta = this.props
+                const localDelta = this._props
                     .ScreenToLocalTransform()
-                    .scale(this.props.NativeDimScaling?.() || 1)
+                    .scale(this._props.NativeDimScaling?.() || 1)
                     .transformDirection(delta[0], delta[1]);
                 const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']);
                 const nativeHeight = NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight']);
                 const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth);
-                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth;
+                const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this._props.NativeDimScaling?.() || 1)) / nativeWidth;
                 if (ratio >= 1) {
                     this.layoutDoc.nativeWidth = nativeWidth * ratio;
                     this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio);
@@ -916,7 +922,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     });
     @action
     onZoomWheel = (e: React.WheelEvent) => {
-        if (this.props.isContentActive(true)) {
+        if (this._props.isContentActive(true)) {
             e.stopPropagation();
         }
     };
@@ -924,14 +930,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (!this.SidebarShown) return 0;
         if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target)
         const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc);
-        return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1);
+        return WebBox.sidebarResizerWidth + nativeDiff * (this._props.NativeDimScaling?.() || 1);
     };
     _innerCollectionView: CollectionFreeFormView | undefined;
     zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
     setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView);
 
     @computed get content() {
-        const interactive = this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
+        const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
         return (
             <div
                 className={'webBox-cont' + (interactive ? '-interactive' : '')}
@@ -959,7 +965,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 {this.inlineTextAnnotations
                     .sort((a, b) => NumCast(a.y) - NumCast(b.y))
                     .map(anno => (
-                        <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
+                        <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />
                     ))}
             </div>
         );
@@ -969,13 +975,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
     renderAnnotations = (childFilters: () => string[]) => (
         <CollectionFreeFormView
-            {...this.props}
+            {...this._props}
             setContentView={this.setInnerContent}
             NativeWidth={returnZero}
             NativeHeight={returnZero}
             originTopLeft={false}
             isAnnotationOverlayScrollable={true}
-            renderDepth={this.props.renderDepth + 1}
+            renderDepth={this._props.renderDepth + 1}
             isAnnotationOverlay={true}
             fieldKey={this.annotationKey}
             setPreviewCursor={this.setPreviewCursor}
@@ -1004,11 +1010,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     @computed get renderTransparentAnnotations() {
         return this.renderAnnotations(this.transparentFilter);
     }
-    childPointerEvents = () => (this.props.isContentActive() ? 'all' : undefined);
+    childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined);
     @computed get webpage() {
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
-        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
-        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
+        const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
         return (
             <div
                 className="webBox-outerContent"
@@ -1021,7 +1027,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 onWheel={this.onZoomWheel}
                 onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this.props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
+                <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this._props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}>
                     {this.content}
                     <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div>
                     {this.renderOpaqueAnnotations}
@@ -1033,7 +1039,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     @computed get searchUI() {
         return (
-            <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}>
+            <div className="webBox-ui" onPointerDown={e => e.stopPropagation()} style={{ display: this._props.isContentActive() ? 'flex' : 'none' }}>
                 <div className="webBox-overlayCont" onPointerDown={e => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
                     <button className="webBox-overlayButton" title={'search'} />
                     <input
@@ -1067,27 +1073,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     }
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func);
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
+    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
+    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
+    scrollXf = () => this._props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
-    transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter];
-    opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])];
+    transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
+    opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (doc instanceof Doc && property === StyleProp.PointerEvents) {
             if (this.inlineTextAnnotations.includes(doc)) return 'none';
         }
-        return this.props.styleProvider?.(doc, props, property);
+        return this._props.styleProvider?.(doc, props, property);
     };
     pointerEvents = () =>
-        !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
+        !this._draggingSidebar && this._props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown()
             ? 'all' //
             : 'none';
-    annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
+    annotationPointerEvents = () => (this._props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none');
     render() {
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
-        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
-        const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
+        const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as any);
+        const scale = previewScale * (this._props.NativeDimScaling?.() || 1);
         return (
             <div
                 className="webBox"
@@ -1096,7 +1102,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     pointerEvents: this.pointerEvents(), //
                     position: SnappingManager.GetIsDragging() ? 'absolute' : undefined,
                 }}>
-                <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} />
+                <div className="webBox-background" style={{ backgroundColor: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }} />
                 <div
                     className="webBox-container"
                     style={{
@@ -1115,9 +1121,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                             anchorMenuClick={this.anchorMenuClick}
                             scrollTop={NumCast(this.layoutDoc.layout_scrollTop)}
                             annotationLayerScrollTop={0}
-                            scaling={this.props.NativeDimScaling}
+                            scaling={this._props.NativeDimScaling}
                             addDocument={this.addDocumentWrapper}
-                            docView={this.props.DocumentView!}
+                            docView={this._props.DocumentView!}
                             finishMarquee={this.finishMarquee}
                             savedAnnotations={this.savedAnnotationsCreator}
                             selectionText={this.selectionText}
@@ -1135,25 +1141,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     }}
                     onPointerDown={e => this.sidebarBtnDown(e, false)}
                 />
-                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this._props.PanelWidth()}%` }}>
                     <SidebarAnnos
                         ref={this._sidebarRef}
-                        {...this.props}
+                        {...this._props}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         fieldKey={this.fieldKey + '_' + this._urlHash}
                         Document={this.Document}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
                         setHeight={emptyFunction}
-                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this.props.NativeDimScaling?.() || 1)}
+                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth) - WebBox.sidebarResizerWidth / (this._props.NativeDimScaling?.() || 1)}
                         showSidebar={this.SidebarShown}
                         sidebarAddDocument={this.sidebarAddDocument}
                         moveDocument={this.moveDocument}
                         removeDocument={this.removeDocument}
                     />
                 </div>
-                {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle}
-                {!this.props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI}
+                {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.sidebarHandle}
+                {!this._props.isContentActive() || SnappingManager.GetIsDragging() ? null : this.searchUI}
             </div>
         );
     }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 19e14d5a7..e2cceb906 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as ReactDOM from 'react-dom/client';
 import { Doc } from '../../../../fields/Doc';
@@ -8,7 +8,7 @@ import { List } from '../../../../fields/List';
 import { listSpec } from '../../../../fields/Schema';
 import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
 import { Cast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { copyProps, emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { DocServer } from '../../../DocServer';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@@ -70,10 +70,10 @@ export class DashFieldView {
             } catch {}
         });
     }
-    @action deselectNode() {
+    deselectNode() {
         this.dom.classList.remove('ProseMirror-selectednode');
     }
-    @action selectNode() {
+    selectNode() {
         this.dom.classList.add('ProseMirror-selectednode');
     }
 }
@@ -100,17 +100,24 @@ 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._fieldKey = this.props.fieldKey;
-        this._textBoxDoc = this.props.tbox.Document;
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._fieldKey = this._props.fieldKey;
+        this._textBoxDoc = this._props.tbox.Document;
 
-        if (this.props.docId) {
-            DocServer.GetRefField(this.props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+        if (this._props.docId) {
+            DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
         } else {
-            this._dashDoc = this.props.tbox.Document;
+            this._dashDoc = this._props.tbox.Document;
         }
     }
+    componentDidUpdate(prevProps: Readonly<IDashFieldViewInternal>, prevState: Readonly<{}>, snapshot?: any): void {
+        copyProps(this);
+    }
     componentWillUnmount() {
         this._reactionDisposer?.();
     }
@@ -119,18 +126,18 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     // set the display of the field's value (checkbox for booleans, span of text for strings)
     @computed get fieldValueContent() {
         return !this._dashDoc ? null : (
-            <div onClick={action(e => (this._expanded = !this.props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this.props.hideKey ? this.props.tbox.props.PanelWidth() - 20 : undefined }}>
+            <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}>
                 <SchemaTableCell
                     Document={this._dashDoc}
                     col={0}
                     deselectCell={emptyFunction}
                     selectCell={emptyFunction}
-                    maxWidth={this.props.hideKey ? undefined : this.return100}
-                    columnWidth={this.props.hideKey ? () => this.props.tbox._props.PanelWidth() - 20 : returnZero}
+                    maxWidth={this._props.hideKey ? undefined : this.return100}
+                    columnWidth={this._props.hideKey ? () => this._props.tbox._props.PanelWidth() - 20 : returnZero}
                     selectedCell={() => [this._dashDoc!, 0]}
                     fieldKey={this._fieldKey}
                     rowHeight={returnZero}
-                    isRowActive={() => this._expanded && this.props.editable}
+                    isRowActive={() => this._expanded && this._props.editable}
                     padding={0}
                     getFinfo={emptyFunction}
                     setColumnValues={returnFalse}
@@ -145,7 +152,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
     }
 
     createPivotForField = (e: React.MouseEvent) => {
-        let container = this.props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
+        let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
         if (container) {
             const embedding = Doc.MakeEmbedding(container.Document);
             embedding._type_collection = CollectionViewType.Time;
@@ -157,7 +164,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
             list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
             embedding._pivotField = this._fieldKey.startsWith('#') ? 'tags' : this._fieldKey;
-            this.props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
+            this._props.tbox._props.addDocTab(embedding, OpenWhere.addRight);
         }
     };
 
@@ -175,17 +182,17 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             <div
                 className="dashFieldView"
                 style={{
-                    width: this.props.width,
-                    height: this.props.height,
-                    pointerEvents: this.props.tbox._props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+                    width: this._props.width,
+                    height: this._props.height,
+                    pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
                 }}>
-                {this.props.hideKey ? null : (
+                {this._props.hideKey ? null : (
                     <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
                         {this._fieldKey}
                     </span>
                 )}
 
-                {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+                {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
             </div>
         );
     }
@@ -198,7 +205,7 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
         super(props);
         DashFieldViewMenu.Instance = this;
     }
-    @action
+
     showFields = (e: React.MouseEvent) => {
         DashFieldViewMenu.createFieldView(e);
         DashFieldViewMenu.Instance.fadeOut(true);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 244de7849..6b1df4560 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -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 } from '../LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
 import { DashDocView } from './DashDocView';
@@ -1855,7 +1855,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash.
     };
     onScroll = (e: React.UIEvent) => {
-        if (!LinkDocPreview.LinkInfo && this._scrollRef.current) {
+        if (!LinkInfo.Instance?.LinkInfo && this._scrollRef.current) {
             if (!this._props.dontSelectOnLoad) {
                 this._ignoreScroll = true;
                 this.layoutDoc._layout_scrollTop = this._scrollRef.current.scrollTop;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 4212d46eb..be8736525 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -3,7 +3,7 @@ import { EditorState, NodeSelection } from 'prosemirror-state';
 import { EditorView } from 'prosemirror-view';
 import { Doc } from '../../../../fields/Doc';
 import { DocServer } from '../../../DocServer';
-import { LinkDocPreview } from '../LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from '../LinkDocPreview';
 import { FormattedTextBox } from './FormattedTextBox';
 import './FormattedTextBoxComment.scss';
 import { schema } from './schema_rts';
@@ -133,7 +133,7 @@ export class FormattedTextBoxComment {
             const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef;
             //nbef &&
             naft &&
-                LinkDocPreview.SetLinkInfo({
+                LinkInfo.SetLinkInfo({
                     docProps: textBox.props,
                     linkSrc: textBox.Document,
                     linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 62df77c0a..db815eae3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,6 +1,5 @@
 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 { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Height } from '../../../fields/DocSymbols';
@@ -17,20 +16,20 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
-import { LinkDocPreview } from '../nodes/LinkDocPreview';
+import { LinkDocPreview, LinkInfo } from '../nodes/LinkDocPreview';
 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';
-const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer');
-const pdfjsLib = require('pdfjs-dist');
+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`;
 // The workerSrc property shall be specified.
-pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.16.105/build/pdf.worker.js';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.0.269/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     Document: Doc;
@@ -52,11 +51,6 @@ interface IViewerProps extends FieldViewProps {
 @observer
 export class PDFViewer extends React.Component<IViewerProps> {
     static _annotationStyle: any = addStyleSheet();
-    @observable private _pageSizes: { width: number; height: number }[] = [];
-    @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
-    @observable private _textSelecting = true;
-    @observable private _showWaiting = true;
-    @observable private Index: number = -1;
 
     _prevProps: IViewerProps;
     @observable _props: IViewerProps;
@@ -65,6 +59,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
         this._prevProps = this._props = props;
         makeObservable(this);
     }
+
+    @observable _pageSizes: { width: number; height: number }[] = [];
+    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+    @observable _textSelecting = true;
+    @observable _showWaiting = true;
+    @observable Index: number = -1;
+
     componentDidUpdate() {
         copyProps(this);
     }
@@ -101,7 +102,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this.allAnnotations.filter(a => a.text_inlineAnnotations);
     }
 
-    componentDidMount = async () => {
+    componentDidMount() {
         runInAction(() => (this._showWaiting = true));
         this.setupPdfJsViewer();
         this._mainCont.current?.addEventListener('scroll', e => ((e.target as any).scrollLeft = 0));
@@ -126,7 +127,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             page => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page),
             { fireImmediately: true }
         );
-    };
+    }
 
     componentWillUnmount = () => {
         Object.values(this._disposers).forEach(disposer => disposer?.());
@@ -262,7 +263,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         document.removeEventListener('copy', this.copy);
         document.addEventListener('copy', this.copy);
-        const eventBus = new PDFJSViewer.EventBus(true);
+        const eventBus = new PDFJSViewer.EventBus();
         eventBus._on('pagesinit', this.pagesinit);
         eventBus._on(
             'pagerendered',
@@ -272,10 +273,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
         const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
         this._pdfViewer = new PDFJSViewer.PDFViewer({
             container: this._mainCont.current,
-            viewer: this._viewer.current,
+            viewer: this._viewer.current || undefined,
             linkService: pdfLinkService,
             findController: pdfFindController,
-            renderer: 'canvas',
             eventBus,
         });
         pdfLinkService.setViewer(this._pdfViewer);
@@ -313,7 +313,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     onScroll = (e: React.UIEvent<HTMLElement>) => {
         if (this._mainCont.current && !this._forcedScroll) {
             this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation)
-            if (!LinkDocPreview.LinkInfo) {
+            if (!LinkInfo.Instance?.LinkInfo) {
                 this._props.layoutDoc._layout_scrollTop = this._mainCont.current.scrollTop;
             }
             this._ignoreScroll = false;
diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts
index e419d3ac4..d634a241d 100644
--- a/src/server/ApiManagers/PDFManager.ts
+++ b/src/server/ApiManagers/PDFManager.ts
@@ -1,31 +1,26 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
-import { existsSync, createReadStream, createWriteStream } from "fs";
-import * as Pdfjs from 'pdfjs-dist/legacy/build/pdf';
-import { createCanvas } from "canvas";
-const imageSize = require("probe-image-size");
-import * as express from "express";
-import * as path from "path";
-import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager";
-import { red } from "colors";
-import { resolve } from "path";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { existsSync, createReadStream, createWriteStream } from 'fs';
+//import * as Pdfjs from 'pdfjs-dist';
+import { createCanvas } from 'canvas';
+const imageSize = require('probe-image-size');
+import * as express from 'express';
+import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './UploadManager';
+import { red } from 'colors';
+import { resolve } from 'path';
 
 export default class PDFManager extends ApiManager {
-
     protected initialize(register: Registration): void {
-
         register({
             method: Method.POST,
-            subscription: new RouteSubscriber("thumbnail"),
+            subscription: new RouteSubscriber('thumbnail'),
             secureHandler: async ({ req, res }) => {
                 const { coreFilename, pageNum, subtree } = req.body;
                 return getOrCreateThumbnail(coreFilename, pageNum, res, subtree);
-            }
+            },
         });
-
     }
-
 }
 
 async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise<void> {
@@ -51,7 +46,7 @@ async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res:
 }
 
 async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) {
-    const part1 = subtree ?? "";
+    const part1 = subtree ?? '';
     const filename = `${part1}${coreFilename}.pdf`;
     const sourcePath = resolve(pathToDirectory(Directory.pdfs), filename);
     const documentProxy = await Pdfjs.getDocument(sourcePath).promise;
@@ -62,7 +57,7 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre
     const renderContext = {
         canvasContext: context,
         canvasFactory: factory,
-        viewport
+        viewport,
     };
     await page.render(renderContext).promise;
     const pngStream = canvas.createPNGStream();
@@ -71,11 +66,11 @@ async function CreateThumbnail(coreFilename: string, pageNum: number, res: expre
     const out = createWriteStream(pngFile);
     pngStream.pipe(out);
     return new Promise<void>((resolve, reject) => {
-        out.on("finish", () => {
+        out.on('finish', () => {
             dispatchThumbnail(res, viewport, resolved);
             resolve();
         });
-        out.on("error", error => {
+        out.on('error', error => {
             console.log(red(`In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`));
             console.log(error);
             reject();
@@ -87,30 +82,29 @@ function dispatchThumbnail(res: express.Response, { width, height }: Pdfjs.PageV
     res.send({
         path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName),
         width,
-        height
+        height,
     });
 }
 
 class NodeCanvasFactory {
-
     create = (width: number, height: number) => {
         const canvas = createCanvas(width, height);
         const context = canvas.getContext('2d');
         return {
             canvas,
-            context
+            context,
         };
-    }
+    };
 
     reset = (canvasAndContext: any, width: number, height: number) => {
         canvasAndContext.canvas.width = width;
         canvasAndContext.canvas.height = height;
-    }
+    };
 
     destroy = (canvasAndContext: any) => {
         canvasAndContext.canvas.width = 0;
         canvasAndContext.canvas.height = 0;
         canvasAndContext.canvas = null;
         canvasAndContext.context = null;
-    }
+    };
 }
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index dd2a857d9..46f521bc9 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -607,19 +607,22 @@ export namespace DashUploadUtils {
         )); // prettier-ignore
 
         const fileIn = fs.readFileSync(sourcePath);
-        return autorotate.rotate(fileIn, { quality: 30 }).then(({ buffer }: { buffer: any }) =>
-            Jimp.read(buffer)
-                .then(async img => {
-                    await Promise.all( sizes.filter(({ width }) => width)  .map(({ width, suffix }) => 
+        let buffer: any;
+        try {
+            const { buffer2 } = await autorotate.rotate(fileIn, { quality: 30 });
+            buffer = buffer2;
+        } catch (e) {}
+        return Jimp.read(buffer ?? fileIn)
+            .then(async img => {
+                await Promise.all( sizes.filter(({ width }) => width)  .map(({ width, suffix }) => 
                         img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
                     )); // prettier-ignore
-                    return writtenFiles;
-                })
-                .catch(e => {
-                    console.log('ERROR' + e);
-                    return writtenFiles;
-                })
-        );
+                return writtenFiles;
+            })
+            .catch(e => {
+                console.log('ERROR' + e);
+                return writtenFiles;
+            });
     }
 
     /**
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index 284aae338..710b0211e 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -8,6 +8,7 @@ declare module 'bezier-curve';
 declare module 'fit-curve';
 declare module 'react-audio-waveform';
 declare module 'iink-js';
+declare module 'pdfjs-dist/web/pdf_viewer';
 
 declare module 'reveal';
 declare module 'react-reveal';
diff --git a/webpack.config.js b/webpack.config.js
index 57dfd222b..c1464ac2a 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -9,17 +9,17 @@ const plugins = [
     new HtmlWebpackPlugin({
         title: 'Caching',
     }),
-    new ForkTsCheckerWebpackPlugin({
-        // typescript: {
-        //     //useTypescriptIncrementalApi: true,
-        //     //tslint: true,
-        //     //memoryLimit: 4096,
-        // },
-        tslint: true,
-        memoryLimit: 4096,
-        useTypescriptIncrementalApi: true,
-        tslintAutoFix: true,
-    }),
+    // new ForkTsCheckerWebpackPlugin({
+    //     typescript: {
+    //         // useTypescriptIncrementalApi: true,
+    //         // tslint: true,
+    //         memoryLimit: 4096,
+    //     },
+    //     // tslint: true,
+    //     // memoryLimit: 4096,
+    //     // useTypescriptIncrementalApi: true,
+    //     // tslintAutoFix: true,
+    // }),
     new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }),
     new webpack.ProvidePlugin({ process: 'process/browser' }),
     new webpack.HotModuleReplacementPlugin(),
-- 
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/PropertiesDocContextSelector.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

